xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/liteparse/liteparse_test.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1// Copyright 2018 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
15package liteparse
16
17import (
18	"context"
19	"fmt"
20	"io/ioutil"
21	"log"
22	"os"
23	"path/filepath"
24	"reflect"
25	"sort"
26	"testing"
27
28	"src/common/golang/runfilelocation"
29	rdpb "src/tools/ak/res/proto/res_data_go_proto"
30	"src/tools/ak/res/res"
31	"src/tools/ak/res/respipe/respipe"
32	"github.com/google/go-cmp/cmp"
33)
34
35const (
36	testdata = "src/tools/ak/liteparse/testdata/"
37)
38
39func TestPathAsRes(t *testing.T) {
40	tests := []struct {
41		arg  string
42		name string
43		ok   bool
44	}{
45		{
46			"foo/bar/res/values/strings.xml",
47			"",
48			false,
49		},
50		{
51			"foo/bar/res/values-ldpi-v19/strings.xml",
52			"",
53			false,
54		},
55		{
56			"foo/bar/res/layout-en-US-v19/hello_america.xml",
57			"hello_america",
58			true,
59		},
60		{
61			"foo/bar/res/xml-land/perfs.xml",
62			"perfs",
63			true,
64		},
65		{
66			"foo/bar/res/drawable-land/eagle.png",
67			"eagle",
68			true,
69		},
70		{
71			"foo/bar/res/raw/vid.1080p.png",
72			"vid.1080p",
73			true,
74		},
75		{
76			"foo/bar/res/drawable-land/circle.9.png",
77			"circle",
78			true,
79		},
80	}
81
82	for _, tc := range tests {
83		pi, err := res.ParsePath(tc.arg)
84		if err != nil {
85			t.Errorf("res.ParsePath(%q) returns %v unexpectedly", tc.arg, err)
86			continue
87		}
88		rawName, ok := pathAsRes(&pi)
89		if tc.name != rawName || ok != tc.ok {
90			t.Errorf("pathAsRes(%v) got %q, %t want %q, %t", pi, rawName, ok, tc.name, tc.ok)
91		}
92	}
93}
94
95func TestNeedsParse(t *testing.T) {
96	tests := []struct {
97		arg     string
98		content string
99		want    bool
100	}{
101		{
102			"foo/bar/res/values/strings.xml",
103			"",
104			true,
105		},
106		{
107			"foo/bar/res/values-ldpi-v19/strings.xml",
108			"",
109			true,
110		},
111		{
112			"foo/bar/res/layout-en-US-v19/hello_america.xml",
113			"",
114			true,
115		},
116		{
117			"foo/bar/res/xml-land/perfs.xml",
118			"",
119			true,
120		},
121		{
122			"foo/bar/res/drawable-land/eagle.png",
123			"",
124			false,
125		},
126		{
127			"foo/bar/res/drawable-land/eagle",
128			"",
129			false,
130		},
131		{
132			"foo/bar/res/drawable-land/eagle_xml",
133			"<?xml version=\"1.0\" encoding=\"utf-8\"?></xml>",
134			true,
135		},
136		{
137			"foo/bar/res/drawable-land/eagle_txt",
138			"some non-xml file",
139			false,
140		},
141	}
142
143	for _, tc := range tests {
144		f := createTestFile(tc.arg, tc.content)
145		defer os.Remove(f)
146		pi, err := res.ParsePath(f)
147		if err != nil {
148			t.Errorf("res.ParsePath(%s) returns %v unexpectedly", f, err)
149			continue
150		}
151		got, err := needsParse(&pi)
152		if err != nil {
153			t.Errorf("needsParse(%v) returns %v unexpectedly", pi, err)
154		}
155		if got != tc.want {
156			t.Errorf("needsParse(%v) got %t want %t", pi, got, tc.want)
157		}
158	}
159}
160
161func createTestFile(path, content string) string {
162	dir := filepath.Dir(path)
163	tmpDir, err := ioutil.TempDir("", "test")
164	if err != nil {
165		log.Fatal(err)
166	}
167	err = os.MkdirAll(tmpDir+"/"+dir, os.ModePerm)
168	if err != nil {
169		log.Fatal(err)
170	}
171	f, err := os.Create(tmpDir + "/" + path)
172	if err != nil {
173		log.Fatal(err)
174	}
175	if _, err := f.Write([]byte(content)); err != nil {
176		log.Fatal(err)
177	}
178	if err := f.Close(); err != nil {
179		log.Fatal(err)
180	}
181	return f.Name()
182}
183
184func TestParse(t *testing.T) {
185	ctx, cancel := context.WithCancel(context.Background())
186	defer cancel()
187	testRes := createResFile("res")
188	piC, pathErrC := respipe.EmitPathInfosDir(ctx, testRes)
189	resC, parseErrC := ResParse(ctx, piC)
190	errC := respipe.MergeErrStreams(ctx, []<-chan error{pathErrC, parseErrC})
191	var parsedNames []string
192	for resC != nil || errC != nil {
193		select {
194		case r, ok := <-resC:
195			if !ok {
196				resC = nil
197				continue
198			}
199			pn, err := res.ParseName(r.GetName(), res.Type(r.ResourceType))
200			if err != nil {
201				t.Errorf("res.ParseName(%q, %v) unexpected err: %v", r.GetName(), r.ResourceType, err)
202				fmt.Printf("parsename err: %v\n", err)
203				continue
204			}
205			parsedNames = append(parsedNames, pn.String())
206		case e, ok := <-errC:
207			if !ok {
208				errC = nil
209				continue
210			}
211			t.Errorf("Unexpected err: %v", e)
212		}
213	}
214	sort.Strings(parsedNames)
215	expectedNames := []string{
216		"res-auto:attr/bg",
217		"res-auto:attr/size",
218		"res-auto:drawable/foo",
219		"res-auto:id/item1",
220		"res-auto:id/item2",
221		"res-auto:id/large",
222		"res-auto:id/response",
223		"res-auto:id/small",
224		"res-auto:menu/simple",
225		"res-auto:raw/garbage",
226		"res-auto:string/exlusive",
227		"res-auto:string/greeting",
228		"res-auto:string/lonely",
229		"res-auto:string/title",
230		"res-auto:string/title2",
231		"res-auto:string/version",
232		"res-auto:string/version", // yes duplicated (appears in 2 different files, dupes get handled later in the pipeline)
233		"res-auto:styleable/absPieChart",
234	}
235	if !reflect.DeepEqual(parsedNames, expectedNames) {
236		t.Errorf("%s: has these resources: %s expected: %s", testRes, parsedNames, expectedNames)
237	}
238}
239
240func TestParseAll(t *testing.T) {
241	tests := []struct {
242		resfiles []string
243		pkg      string
244		want     *rdpb.Resources
245	}{
246		{
247			resfiles: createResfiles([]string{}),
248			pkg:      "",
249			want:     createResources("", []rdpb.Resource_Type{}, []string{}),
250		},
251		{
252			resfiles: createResfiles([]string{"mini-1"}),
253			pkg:      "example",
254			want:     createResources("example", []rdpb.Resource_Type{rdpb.Resource_STRING}, []string{"greeting"}),
255		},
256		{
257			resfiles: createResfiles([]string{"mini-2"}),
258			pkg:      "com.example",
259			want:     createResources("com.example", []rdpb.Resource_Type{rdpb.Resource_XML, rdpb.Resource_ID}, []string{"foo", "foobar"}),
260		},
261		{
262			resfiles: createResfiles([]string{"res/drawable-ldpi/foo.9.png", "res/menu/simple.xml"}),
263			pkg:      "com.example",
264			want: createResources("com.example",
265				[]rdpb.Resource_Type{rdpb.Resource_DRAWABLE, rdpb.Resource_MENU, rdpb.Resource_ID, rdpb.Resource_ID},
266				[]string{"foo", "simple", "item1", "item2"}),
267		},
268	}
269
270	for _, tc := range tests {
271		if got := ParseAll(context.Background(), tc.resfiles, tc.pkg); !resourcesEqual(got, tc.want) {
272			t.Errorf("ParseAll(%v, %v) = {%v}, want {%v}", tc.resfiles, tc.pkg, got, tc.want)
273		}
274	}
275}
276
277func TestParseAllContents(t *testing.T) {
278	tests := []struct {
279		resfiles []string
280		pkg      string
281		want     *rdpb.Resources
282	}{
283		{
284			resfiles: createResfiles([]string{}),
285			pkg:      "",
286			want:     createResources("", []rdpb.Resource_Type{}, []string{}),
287		},
288		{
289			resfiles: createResfiles([]string{"mini-1/res/values/strings.xml"}),
290			pkg:      "example",
291			want:     createResources("example", []rdpb.Resource_Type{rdpb.Resource_STRING}, []string{"greeting"}),
292		},
293		{
294			resfiles: createResfiles([]string{"mini-2/res/xml/foo.xml"}),
295			pkg:      "com.example",
296			want:     createResources("com.example", []rdpb.Resource_Type{rdpb.Resource_XML, rdpb.Resource_ID}, []string{"foo", "foobar"}),
297		},
298		{
299			resfiles: createResfiles([]string{"res/drawable-ldpi/foo.9.png", "res/menu/simple.xml"}),
300			pkg:      "com.example",
301			want: createResources("com.example",
302				[]rdpb.Resource_Type{rdpb.Resource_DRAWABLE, rdpb.Resource_MENU, rdpb.Resource_ID, rdpb.Resource_ID},
303				[]string{"foo", "simple", "item1", "item2"}),
304		},
305	}
306
307	for _, tc := range tests {
308		allContents := getAllContents(t, tc.resfiles)
309		got, err := ParseAllContents(context.Background(), tc.resfiles, allContents, tc.pkg)
310		if err != nil {
311			t.Errorf("ParseAllContents(%v, %v) failed with error %v", tc.resfiles, tc.pkg, err)
312		}
313		if !resourcesEqual(got, tc.want) {
314			t.Errorf("ParseAllContents(%v, %v) = {%v}, want {%v}", tc.resfiles, tc.pkg, got, tc.want)
315		}
316	}
317}
318
319// createResFile creates filename with the testdata as the base
320func createResFile(filename string) string {
321	fullPath := testdata + filename
322	resFilePath, err := runfilelocation.Find(fullPath)
323	if err != nil {
324		log.Fatalf("Could not find the runfile at %v", resFilePath)
325	}
326	return resFilePath
327}
328
329// createResfiles creates filenames with the testdata as the base
330func createResfiles(filenames []string) []string {
331	var resfiles []string
332	for _, filename := range filenames {
333		resfiles = append(resfiles, createResFile(filename))
334	}
335	return resfiles
336}
337
338func getAllContents(t *testing.T, paths []string) [][]byte {
339	var allContents [][]byte
340	for _, path := range paths {
341		contents, err := os.ReadFile(path)
342		if err != nil {
343			t.Errorf("cannot read file %v: %v", path, err)
344		}
345		allContents = append(allContents, contents)
346	}
347	return allContents
348}
349
350// createResources creates rdpb.Resources with package name pkg and resources {names[i], resource[i]}
351func createResources(pkg string, resources []rdpb.Resource_Type, names []string) *rdpb.Resources {
352	rscs := &rdpb.Resources{
353		Pkg: pkg,
354	}
355	for i := 0; i < len(names); i++ {
356		r := &rdpb.Resource{Name: names[i], ResourceType: resources[i]}
357		rscs.Resource = append(rscs.Resource, r)
358	}
359	return rscs
360}
361
362// resourcesEqual checks if the two resources have the same package names and resources
363func resourcesEqual(rscs1 *rdpb.Resources, rscs2 *rdpb.Resources) bool {
364	return rscs1.Pkg == rscs2.Pkg && cmp.Equal(createResourcesMap(rscs1), createResourcesMap(rscs2))
365}
366
367// createResourcesMap creates a map of resources contained in rscs that maps the rdpb.Resource_Type to the names and the number of times the name appears.
368func createResourcesMap(rscs *rdpb.Resources) map[rdpb.Resource_Type]map[string]int {
369	m := make(map[rdpb.Resource_Type]map[string]int)
370	for _, r := range rscs.Resource {
371		if _, ok := m[r.GetResourceType()]; !ok {
372			m[r.GetResourceType()] = make(map[string]int)
373			m[r.GetResourceType()][r.GetName()] = 1
374		} else if _, ok := m[r.GetResourceType()][r.GetName()]; !ok {
375			m[r.GetResourceType()][r.GetName()] = 1
376		} else {
377			m[r.GetResourceType()][r.GetName()]++
378		}
379	}
380	return m
381}
382