1// Copyright (C) 2021 The Android Open Source Project
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 apex
16
17import (
18	"reflect"
19	"testing"
20
21	"android/soong/android"
22	"android/soong/java"
23	"github.com/google/blueprint"
24)
25
26// Contains tests for java.CreateClasspathElements logic from java/classpath_element.go that
27// requires apexes.
28
29// testClasspathElementContext is a ClasspathElementContext suitable for use in tests.
30type testClasspathElementContext struct {
31	testContext *android.TestContext
32	module      android.Module
33	errs        []error
34}
35
36func (t *testClasspathElementContext) OtherModuleHasProvider(module blueprint.Module, provider blueprint.ProviderKey) bool {
37	return t.testContext.ModuleHasProvider(module, provider)
38}
39
40func (t *testClasspathElementContext) OtherModuleProvider(module blueprint.Module, provider blueprint.ProviderKey) interface{} {
41	return t.testContext.ModuleProvider(module, provider)
42}
43
44func (t *testClasspathElementContext) ModuleErrorf(fmt string, args ...interface{}) {
45	t.errs = append(t.errs, t.testContext.ModuleErrorf(t.module, fmt, args...))
46}
47
48var _ java.ClasspathElementContext = (*testClasspathElementContext)(nil)
49
50func TestCreateClasspathElements(t *testing.T) {
51	preparer := android.GroupFixturePreparers(
52		prepareForTestWithPlatformBootclasspath,
53		prepareForTestWithArtApex,
54		prepareForTestWithMyapex,
55		// For otherapex.
56		android.FixtureMergeMockFs(android.MockFS{
57			"system/sepolicy/apex/otherapex-file_contexts": nil,
58		}),
59		java.PrepareForTestWithJavaSdkLibraryFiles,
60		java.FixtureWithLastReleaseApis("foo", "othersdklibrary"),
61		android.FixtureWithRootAndroidBp(`
62		apex {
63			name: "com.android.art",
64			key: "com.android.art.key",
65 			bootclasspath_fragments: [
66				"art-bootclasspath-fragment",
67			],
68			java_libs: [
69				"othersdklibrary",
70			],
71			updatable: false,
72		}
73
74		apex_key {
75			name: "com.android.art.key",
76			public_key: "com.android.art.avbpubkey",
77			private_key: "com.android.art.pem",
78		}
79
80		bootclasspath_fragment {
81			name: "art-bootclasspath-fragment",
82			apex_available: [
83				"com.android.art",
84			],
85			contents: [
86				"baz",
87				"quuz",
88			],
89		}
90
91		java_library {
92			name: "baz",
93			apex_available: [
94				"com.android.art",
95			],
96			srcs: ["b.java"],
97			installable: true,
98		}
99
100		java_library {
101			name: "quuz",
102			apex_available: [
103				"com.android.art",
104			],
105			srcs: ["b.java"],
106			installable: true,
107		}
108
109		apex {
110			name: "myapex",
111			key: "myapex.key",
112 			bootclasspath_fragments: [
113				"mybootclasspath-fragment",
114			],
115			java_libs: [
116				"othersdklibrary",
117			],
118			updatable: false,
119		}
120
121		apex_key {
122			name: "myapex.key",
123			public_key: "testkey.avbpubkey",
124			private_key: "testkey.pem",
125		}
126
127		bootclasspath_fragment {
128			name: "mybootclasspath-fragment",
129			apex_available: [
130				"myapex",
131			],
132			contents: [
133				"bar",
134			],
135		}
136
137		java_library {
138			name: "bar",
139			srcs: ["b.java"],
140			installable: true,
141			apex_available: ["myapex"],
142			permitted_packages: ["bar"],
143		}
144
145		java_sdk_library {
146			name: "foo",
147			srcs: ["b.java"],
148		}
149
150		java_sdk_library {
151			name: "othersdklibrary",
152			srcs: ["b.java"],
153			shared_library: false,
154			apex_available: [
155				"com.android.art",
156				"myapex",
157			],
158		}
159
160		bootclasspath_fragment {
161			name: "non-apex-fragment",
162			contents: ["othersdklibrary"],
163		}
164
165		apex {
166			name: "otherapex",
167			key: "otherapex.key",
168			java_libs: [
169				"otherapexlibrary",
170			],
171			updatable: false,
172		}
173
174		apex_key {
175			name: "otherapex.key",
176			public_key: "testkey.avbpubkey",
177			private_key: "testkey.pem",
178		}
179
180		java_library {
181			name: "otherapexlibrary",
182			srcs: ["b.java"],
183			installable: true,
184			apex_available: ["otherapex"],
185			permitted_packages: ["otherapexlibrary"],
186		}
187
188		platform_bootclasspath {
189			name: "myplatform-bootclasspath",
190
191			fragments: [
192				{
193					apex: "com.android.art",
194					module: "art-bootclasspath-fragment",
195				},
196			],
197		}
198	`),
199	)
200
201	result := preparer.RunTest(t)
202
203	artFragment := result.Module("art-bootclasspath-fragment", "android_common_apex10000")
204	artBaz := result.Module("baz", "android_common_apex10000")
205	artQuuz := result.Module("quuz", "android_common_apex10000")
206
207	myFragment := result.Module("mybootclasspath-fragment", "android_common_apex10000")
208	myBar := result.Module("bar", "android_common_apex10000")
209
210	nonApexFragment := result.Module("non-apex-fragment", "android_common")
211	other := result.Module("othersdklibrary", "android_common_apex10000")
212
213	otherApexLibrary := result.Module("otherapexlibrary", "android_common_apex10000")
214
215	platformFoo := result.Module("quuz", "android_common")
216
217	bootclasspath := result.Module("myplatform-bootclasspath", "android_common")
218
219	// Use a custom assertion method instead of AssertDeepEquals as the latter formats the output
220	// using %#v which results in meaningless output as ClasspathElements are pointers.
221	assertElementsEquals := func(t *testing.T, message string, expected, actual java.ClasspathElements) {
222		if !reflect.DeepEqual(expected, actual) {
223			t.Errorf("%s: expected:\n  %s\n got:\n  %s", message, expected, actual)
224		}
225	}
226
227	expectFragmentElement := func(module android.Module, contents ...android.Module) java.ClasspathElement {
228		return &java.ClasspathFragmentElement{module, contents}
229	}
230	expectLibraryElement := func(module android.Module) java.ClasspathElement {
231		return &java.ClasspathLibraryElement{module}
232	}
233
234	newCtx := func() *testClasspathElementContext {
235		return &testClasspathElementContext{testContext: result.TestContext, module: bootclasspath}
236	}
237
238	// Verify that CreateClasspathElements works when given valid input.
239	t.Run("art:baz, art:quuz, my:bar, foo", func(t *testing.T) {
240		ctx := newCtx()
241		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, artQuuz, myBar, platformFoo}, []android.Module{artFragment, myFragment})
242		expectedElements := java.ClasspathElements{
243			expectFragmentElement(artFragment, artBaz, artQuuz),
244			expectFragmentElement(myFragment, myBar),
245			expectLibraryElement(platformFoo),
246		}
247		assertElementsEquals(t, "elements", expectedElements, elements)
248	})
249
250	// Verify that CreateClasspathElements detects when a fragment does not have an associated apex.
251	t.Run("non apex fragment", func(t *testing.T) {
252		ctx := newCtx()
253		elements := java.CreateClasspathElements(ctx, []android.Module{}, []android.Module{nonApexFragment})
254		android.FailIfNoMatchingErrors(t, "fragment non-apex-fragment{.*} is not part of an apex", ctx.errs)
255		expectedElements := java.ClasspathElements{}
256		assertElementsEquals(t, "elements", expectedElements, elements)
257	})
258
259	// Verify that CreateClasspathElements detects when an apex has multiple fragments.
260	t.Run("multiple fragments for same apex", func(t *testing.T) {
261		ctx := newCtx()
262		elements := java.CreateClasspathElements(ctx, []android.Module{}, []android.Module{artFragment, artFragment})
263		android.FailIfNoMatchingErrors(t, "apex com.android.art has multiple fragments, art-bootclasspath-fragment{.*} and art-bootclasspath-fragment{.*}", ctx.errs)
264		expectedElements := java.ClasspathElements{}
265		assertElementsEquals(t, "elements", expectedElements, elements)
266	})
267
268	// Verify that CreateClasspathElements detects when a library is in multiple fragments.
269	t.Run("library from multiple fragments", func(t *testing.T) {
270		ctx := newCtx()
271		elements := java.CreateClasspathElements(ctx, []android.Module{other}, []android.Module{artFragment, myFragment})
272		android.FailIfNoMatchingErrors(t, "library othersdklibrary{.*} is in two separate fragments, art-bootclasspath-fragment{.*} and mybootclasspath-fragment{.*}", ctx.errs)
273		expectedElements := java.ClasspathElements{}
274		assertElementsEquals(t, "elements", expectedElements, elements)
275	})
276
277	// Verify that CreateClasspathElements detects when a fragment's contents are not contiguous and
278	// are separated by a library from another fragment.
279	t.Run("discontiguous separated by fragment", func(t *testing.T) {
280		ctx := newCtx()
281		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, myBar, artQuuz, platformFoo}, []android.Module{artFragment, myFragment})
282		expectedElements := java.ClasspathElements{
283			expectFragmentElement(artFragment, artBaz, artQuuz),
284			expectFragmentElement(myFragment, myBar),
285			expectLibraryElement(platformFoo),
286		}
287		assertElementsEquals(t, "elements", expectedElements, elements)
288		android.FailIfNoMatchingErrors(t, "libraries from the same fragment must be contiguous, however baz{.*} and quuz{os:android,arch:common,apex:apex10000} from fragment art-bootclasspath-fragment{.*} are separated by libraries from fragment mybootclasspath-fragment{.*} like bar{.*}", ctx.errs)
289	})
290
291	// Verify that CreateClasspathElements detects when a fragment's contents are not contiguous and
292	// are separated by a standalone library.
293	t.Run("discontiguous separated by library", func(t *testing.T) {
294		ctx := newCtx()
295		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, platformFoo, artQuuz, myBar}, []android.Module{artFragment, myFragment})
296		expectedElements := java.ClasspathElements{
297			expectFragmentElement(artFragment, artBaz, artQuuz),
298			expectLibraryElement(platformFoo),
299			expectFragmentElement(myFragment, myBar),
300		}
301		assertElementsEquals(t, "elements", expectedElements, elements)
302		android.FailIfNoMatchingErrors(t, "libraries from the same fragment must be contiguous, however baz{.*} and quuz{os:android,arch:common,apex:apex10000} from fragment art-bootclasspath-fragment{.*} are separated by library quuz{.*}", ctx.errs)
303	})
304
305	// Verify that CreateClasspathElements detects when there a library on the classpath that
306	// indicates it is from an apex the supplied fragments list does not contain a fragment for that
307	// apex.
308	t.Run("no fragment for apex", func(t *testing.T) {
309		ctx := newCtx()
310		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, otherApexLibrary}, []android.Module{artFragment})
311		expectedElements := java.ClasspathElements{
312			expectFragmentElement(artFragment, artBaz),
313		}
314		assertElementsEquals(t, "elements", expectedElements, elements)
315		android.FailIfNoMatchingErrors(t, `library otherapexlibrary{.*} is from apexes \[otherapex\] which have no corresponding fragment in \[art-bootclasspath-fragment{.*}\]`, ctx.errs)
316	})
317}
318