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 generatemanifest is a command line tool to generate an empty AndroidManifest
16package generatemanifest
17
18import (
19	"io"
20	"strings"
21	"testing"
22
23	"github.com/google/go-cmp/cmp"
24)
25
26type fakeFile struct {
27	reader *strings.Reader
28}
29
30func (f fakeFile) Read(b []byte) (int, error) {
31	return f.reader.Read(b)
32}
33
34func (f fakeFile) Close() error {
35	return nil
36}
37
38func TestExtractMinSdk(t *testing.T) {
39	tests := []struct {
40		name           string
41		manifests      []*strings.Reader
42		defaultMinSdk  int
43		expectedMinSdk int
44	}{
45		{
46			name: "one manifest",
47			manifests: []*strings.Reader{
48				strings.NewReader(
49					`<?xml version="1.0" encoding="utf-8"?>
50<manifest xmlns:android="http://schemas.android.com/apk/res/android"
51		package="com.default">
52		<uses-sdk android:minSdkVersion="20" />
53</manifest>`)},
54			defaultMinSdk:  14,
55			expectedMinSdk: 20,
56		},
57		{
58			name: "one manifest, lower then default min sdk",
59			manifests: []*strings.Reader{
60				strings.NewReader(
61					`<?xml version="1.0" encoding="utf-8"?>
62<manifest xmlns:android="http://schemas.android.com/apk/res/android"
63		package="com.default">
64		<uses-sdk android:minSdkVersion="20" />
65</manifest>`)},
66			defaultMinSdk:  30,
67			expectedMinSdk: 30,
68		},
69		{
70			name: "multiple manifests",
71			manifests: []*strings.Reader{
72				strings.NewReader(
73					`<?xml version="1.0" encoding="utf-8"?>
74<manifest xmlns:android="http://schemas.android.com/apk/res/android"
75		package="com.default">
76		<uses-sdk android:minSdkVersion="20" />
77</manifest>`),
78				strings.NewReader(
79					`<?xml version="1.0" encoding="utf-8"?>
80<manifest xmlns:android="http://schemas.android.com/apk/res/android"
81		package="com.default">
82		<uses-sdk android:minSdkVersion="5" />
83</manifest>`),
84				strings.NewReader(
85					`<?xml version="1.0" encoding="utf-8"?>
86<manifest xmlns:android="http://schemas.android.com/apk/res/android"
87		package="com.default">
88		<uses-sdk android:minSdkVersion="30" />
89</manifest>`),
90			},
91			defaultMinSdk:  14,
92			expectedMinSdk: 30,
93		},
94		{
95			name: "multiple manifests, all lower than default min sdk",
96			manifests: []*strings.Reader{
97				strings.NewReader(
98					`<?xml version="1.0" encoding="utf-8"?>
99<manifest xmlns:android="http://schemas.android.com/apk/res/android"
100		package="com.default">
101		<uses-sdk android:minSdkVersion="1" />
102</manifest>`),
103				strings.NewReader(
104					`<?xml version="1.0" encoding="utf-8"?>
105<manifest xmlns:android="http://schemas.android.com/apk/res/android"
106		package="com.default">
107		<uses-sdk android:minSdkVersion="2" />
108</manifest>`),
109				strings.NewReader(
110					`<?xml version="1.0" encoding="utf-8"?>
111<manifest xmlns:android="http://schemas.android.com/apk/res/android"
112		package="com.default">
113		<uses-sdk android:minSdkVersion="3" />
114</manifest>`),
115			},
116			defaultMinSdk:  4,
117			expectedMinSdk: 4,
118		},
119	}
120	for _, tc := range tests {
121		t.Run(tc.name, func(t *testing.T) {
122			files := make([]io.ReadCloser, 0, len(tc.manifests))
123			for _, f := range tc.manifests {
124				file := fakeFile{reader: f}
125				file.reader.Seek(0, 0)
126				files = append(files, file)
127			}
128			minSdk, err := extractMinSdk(files, tc.defaultMinSdk)
129			if err != nil {
130				t.Fatalf("extractMinSdk(%v, %d) failed with err: %v", files, tc.defaultMinSdk, err)
131			}
132			if diff := cmp.Diff(tc.expectedMinSdk, minSdk); diff != "" {
133				t.Errorf("extractMinSdkFromManifest(%v) returned diff (-want, +got):\n%v", files, diff)
134			}
135		})
136	}
137
138}
139
140func TestExtractMinSdkFromManifest(t *testing.T) {
141	tests := []struct {
142		name           string
143		manifest       *strings.Reader
144		expectedMinSdk int
145	}{
146		{
147			name: "minimal manifest",
148			manifest: strings.NewReader(
149				`<?xml version="1.0" encoding="utf-8"?>
150<manifest xmlns:android="http://schemas.android.com/apk/res/android"
151		package="com.default">
152		<uses-sdk android:minSdkVersion="1" />
153		<application/>
154</manifest>`),
155			expectedMinSdk: 1,
156		},
157		{
158			name: "manifest with placeholder",
159			manifest: strings.NewReader(
160				`<?xml version="1.0" encoding="utf-8"?>
161<manifest xmlns:android="http://schemas.android.com/apk/res/android"
162		package="com.default">
163		<uses-sdk android:minSdkVersion="${minSdkVersion}" />
164		<application/>
165</manifest>`),
166			expectedMinSdk: 0,
167		},
168		{
169			name: "empty manifest",
170			manifest: strings.NewReader(
171				`<?xml version="1.0" encoding="utf-8"?>
172<manifest xmlns:android="http://schemas.android.com/apk/res/android"
173		package="com.default">
174</manifest>`),
175			expectedMinSdk: 0,
176		},
177		{
178			name: "manifest with various elements",
179			manifest: strings.NewReader(
180				`<?xml version="1.0" encoding="utf-8"?>
181<manifest xmlns:android="http://schemas.android.com/apk/res/android"
182		package="com.default">
183    <uses-permission android:name="android.permission.INTERNET" />
184		<application android:label="@string/app_name"
185			android:name="com.default.SomeApp"
186			android:icon="@drawable/some_icon"
187			android:theme="@style/a_theme"
188			android:banner="@drawable/banner">
189		<service android:name="com.default.MyService"
190            android:exported="true">
191            <intent-filter>
192                <action android:name="com.default.IntentFilter" />
193            </intent-filter>
194        </service>
195		</application>
196		<uses-sdk android:minSdkVersion="25" />
197</manifest>`),
198			expectedMinSdk: 25,
199		},
200	}
201	for _, tc := range tests {
202		t.Run(tc.name, func(t *testing.T) {
203			file := fakeFile{reader: tc.manifest}
204			file.reader.Seek(0, 0)
205			results := make(chan result)
206			go func(results chan result) {
207				res := extractMinSdkFromManifest(file)
208				results <- res
209			}(results)
210			result := <-results
211			if result.err != nil {
212				t.Fatalf("extractMinSdkFromManifest(%v) failed with err: %v", file, result.err)
213			}
214			if diff := cmp.Diff(tc.expectedMinSdk, result.minSdk); diff != "" {
215				t.Errorf("extractMinSdkFromManifest(%v) returned diff (-want, +got):\n%v", file, diff)
216			}
217		})
218	}
219
220}
221