• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 Google Inc. 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 dexpreopt
16
17// This file contains unit tests for class loader context structure.
18// For class loader context tests involving .bp files, see TestUsesLibraries in java package.
19
20import (
21	"fmt"
22	"reflect"
23	"strings"
24	"testing"
25
26	"android/soong/android"
27)
28
29func TestCLC(t *testing.T) {
30	// Construct class loader context with the following structure:
31	// .
32	// ├── 29
33	// │   ├── android.hidl.manager
34	// │   └── android.hidl.base
35	// │
36	// └── any
37	//     ├── a
38	//     ├── b
39	//     ├── c
40	//     ├── d
41	//     │   ├── a2
42	//     │   ├── b2
43	//     │   └── c2
44	//     │       ├── a1
45	//     │       └── b1
46	//     ├── f
47	//     ├── a3
48	//     └── b3
49	//
50	ctx := testContext()
51
52	optional := false
53
54	m := make(ClassLoaderContextMap)
55
56	m.AddContext(ctx, AnySdkVersion, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
57	m.AddContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
58	m.AddContext(ctx, AnySdkVersion, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
59
60	// Add some libraries with nested subcontexts.
61
62	m1 := make(ClassLoaderContextMap)
63	m1.AddContext(ctx, AnySdkVersion, "a1", optional, buildPath(ctx, "a1"), installPath(ctx, "a1"), nil)
64	m1.AddContext(ctx, AnySdkVersion, "b1", optional, buildPath(ctx, "b1"), installPath(ctx, "b1"), nil)
65
66	m2 := make(ClassLoaderContextMap)
67	m2.AddContext(ctx, AnySdkVersion, "a2", optional, buildPath(ctx, "a2"), installPath(ctx, "a2"), nil)
68	m2.AddContext(ctx, AnySdkVersion, "b2", optional, buildPath(ctx, "b2"), installPath(ctx, "b2"), nil)
69	m2.AddContext(ctx, AnySdkVersion, "c2", optional, buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
70
71	m3 := make(ClassLoaderContextMap)
72	m3.AddContext(ctx, AnySdkVersion, "a3", optional, buildPath(ctx, "a3"), installPath(ctx, "a3"), nil)
73	m3.AddContext(ctx, AnySdkVersion, "b3", optional, buildPath(ctx, "b3"), installPath(ctx, "b3"), nil)
74
75	m.AddContext(ctx, AnySdkVersion, "d", optional, buildPath(ctx, "d"), installPath(ctx, "d"), m2)
76	// When the same library is both in conditional and unconditional context, it should be removed
77	// from conditional context.
78	m.AddContext(ctx, 42, "f", optional, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
79	m.AddContext(ctx, AnySdkVersion, "f", optional, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
80
81	// Merge map with implicit root library that is among toplevel contexts => does nothing.
82	m.AddContextMap(m1, "c")
83	// Merge map with implicit root library that is not among toplevel contexts => all subcontexts
84	// of the other map are added as toplevel contexts.
85	m.AddContextMap(m3, "m_g")
86
87	// Compatibility libraries with unknown install paths get default paths.
88	m.AddContext(ctx, 29, AndroidHidlManager, optional, buildPath(ctx, AndroidHidlManager), nil, nil)
89	m.AddContext(ctx, 29, AndroidHidlBase, optional, buildPath(ctx, AndroidHidlBase), nil, nil)
90
91	// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
92	// needed as a compatibility library if "android.test.runner" is in CLC as well.
93	m.AddContext(ctx, 30, AndroidTestMock, optional, buildPath(ctx, AndroidTestMock), nil, nil)
94
95	valid, validationError := validateClassLoaderContext(m)
96
97	fixClassLoaderContext(m)
98
99	var haveStr string
100	var havePaths android.Paths
101	var haveUsesLibsReq, haveUsesLibsOpt []string
102	if valid && validationError == nil {
103		haveStr, havePaths = ComputeClassLoaderContext(m)
104		haveUsesLibsReq, haveUsesLibsOpt = m.UsesLibs()
105	}
106
107	// Test that validation is successful (all paths are known).
108	t.Run("validate", func(t *testing.T) {
109		if !(valid && validationError == nil) {
110			t.Errorf("invalid class loader context")
111		}
112	})
113
114	// Test that class loader context structure is correct.
115	t.Run("string", func(t *testing.T) {
116		wantStr := " --host-context-for-sdk 29 " +
117			"PCL[out/soong/" + AndroidHidlManager + ".jar]#" +
118			"PCL[out/soong/" + AndroidHidlBase + ".jar]" +
119			" --target-context-for-sdk 29 " +
120			"PCL[/system/framework/" + AndroidHidlManager + ".jar]#" +
121			"PCL[/system/framework/" + AndroidHidlBase + ".jar]" +
122			" --host-context-for-sdk any " +
123			"PCL[out/soong/a.jar]#PCL[out/soong/b.jar]#PCL[out/soong/c.jar]#PCL[out/soong/d.jar]" +
124			"{PCL[out/soong/a2.jar]#PCL[out/soong/b2.jar]#PCL[out/soong/c2.jar]" +
125			"{PCL[out/soong/a1.jar]#PCL[out/soong/b1.jar]}}#" +
126			"PCL[out/soong/f.jar]#PCL[out/soong/a3.jar]#PCL[out/soong/b3.jar]" +
127			" --target-context-for-sdk any " +
128			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]" +
129			"{PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]" +
130			"{PCL[/system/a1.jar]#PCL[/system/b1.jar]}}#" +
131			"PCL[/system/f.jar]#PCL[/system/a3.jar]#PCL[/system/b3.jar]"
132		if wantStr != haveStr {
133			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
134		}
135	})
136
137	// Test that all expected build paths are gathered.
138	t.Run("paths", func(t *testing.T) {
139		wantPaths := []string{
140			"out/soong/android.hidl.manager-V1.0-java.jar", "out/soong/android.hidl.base-V1.0-java.jar",
141			"out/soong/a.jar", "out/soong/b.jar", "out/soong/c.jar", "out/soong/d.jar",
142			"out/soong/a2.jar", "out/soong/b2.jar", "out/soong/c2.jar",
143			"out/soong/a1.jar", "out/soong/b1.jar",
144			"out/soong/f.jar", "out/soong/a3.jar", "out/soong/b3.jar",
145		}
146		if !reflect.DeepEqual(wantPaths, havePaths.Strings()) {
147			t.Errorf("\nwant paths: %s\nhave paths: %s", wantPaths, havePaths)
148		}
149	})
150
151	// Test for libraries that are added by the manifest_fixer.
152	t.Run("uses libs", func(t *testing.T) {
153		wantUsesLibsReq := []string{"a", "b", "c", "d", "f", "a3", "b3"}
154		wantUsesLibsOpt := []string{}
155		if !reflect.DeepEqual(wantUsesLibsReq, haveUsesLibsReq) {
156			t.Errorf("\nwant required uses libs: %s\nhave required uses libs: %s", wantUsesLibsReq, haveUsesLibsReq)
157		}
158		if !reflect.DeepEqual(wantUsesLibsOpt, haveUsesLibsOpt) {
159			t.Errorf("\nwant optional uses libs: %s\nhave optional uses libs: %s", wantUsesLibsOpt, haveUsesLibsOpt)
160		}
161	})
162}
163
164func TestCLCJson(t *testing.T) {
165	ctx := testContext()
166	optional := false
167	m := make(ClassLoaderContextMap)
168	m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
169	m.AddContext(ctx, 29, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
170	m.AddContext(ctx, 30, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
171	m.AddContext(ctx, AnySdkVersion, "d", optional, buildPath(ctx, "d"), installPath(ctx, "d"), nil)
172	jsonCLC := toJsonClassLoaderContext(m)
173	restored := fromJsonClassLoaderContext(ctx, jsonCLC)
174	android.AssertIntEquals(t, "The size of the maps should be the same.", len(m), len(restored))
175	for k := range m {
176		a, _ := m[k]
177		b, ok := restored[k]
178		android.AssertBoolEquals(t, "The both maps should have the same keys.", ok, true)
179		android.AssertIntEquals(t, "The size of the elements should be the same.", len(a), len(b))
180		for i, elemA := range a {
181			before := fmt.Sprintf("%v", *elemA)
182			after := fmt.Sprintf("%v", *b[i])
183			android.AssertStringEquals(t, "The content should be the same.", before, after)
184		}
185	}
186}
187
188// Test that unknown library paths cause a validation error.
189func testCLCUnknownPath(t *testing.T, whichPath string) {
190	ctx := testContext()
191	optional := false
192
193	m := make(ClassLoaderContextMap)
194	if whichPath == "build" {
195		m.AddContext(ctx, AnySdkVersion, "a", optional, nil, nil, nil)
196	} else {
197		m.AddContext(ctx, AnySdkVersion, "a", optional, buildPath(ctx, "a"), nil, nil)
198	}
199
200	// The library should be added to <uses-library> tags by the manifest_fixer.
201	t.Run("uses libs", func(t *testing.T) {
202		haveUsesLibsReq, haveUsesLibsOpt := m.UsesLibs()
203		wantUsesLibsReq := []string{"a"}
204		wantUsesLibsOpt := []string{}
205		if !reflect.DeepEqual(wantUsesLibsReq, haveUsesLibsReq) {
206			t.Errorf("\nwant required uses libs: %s\nhave required uses libs: %s", wantUsesLibsReq, haveUsesLibsReq)
207		}
208		if !reflect.DeepEqual(wantUsesLibsOpt, haveUsesLibsOpt) {
209			t.Errorf("\nwant optional uses libs: %s\nhave optional uses libs: %s", wantUsesLibsOpt, haveUsesLibsOpt)
210		}
211	})
212
213	// But CLC cannot be constructed: there is a validation error.
214	_, err := validateClassLoaderContext(m)
215	checkError(t, err, fmt.Sprintf("invalid %s path for <uses-library> \"a\"", whichPath))
216}
217
218// Test that unknown build path is an error.
219func TestCLCUnknownBuildPath(t *testing.T) {
220	testCLCUnknownPath(t, "build")
221}
222
223// Test that unknown install path is an error.
224func TestCLCUnknownInstallPath(t *testing.T) {
225	testCLCUnknownPath(t, "install")
226}
227
228// An attempt to add conditional nested subcontext should fail.
229func TestCLCNestedConditional(t *testing.T) {
230	ctx := testContext()
231	optional := false
232	m1 := make(ClassLoaderContextMap)
233	m1.AddContext(ctx, 42, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
234	m := make(ClassLoaderContextMap)
235	err := m.addContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), m1)
236	checkError(t, err, "nested class loader context shouldn't have conditional part")
237}
238
239// Test for SDK version order in conditional CLC: no matter in what order the libraries are added,
240// they end up in the order that agrees with PackageManager.
241func TestCLCSdkVersionOrder(t *testing.T) {
242	ctx := testContext()
243	optional := false
244	m := make(ClassLoaderContextMap)
245	m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
246	m.AddContext(ctx, 29, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
247	m.AddContext(ctx, 30, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
248	m.AddContext(ctx, AnySdkVersion, "d", optional, buildPath(ctx, "d"), installPath(ctx, "d"), nil)
249
250	valid, validationError := validateClassLoaderContext(m)
251
252	fixClassLoaderContext(m)
253
254	var haveStr string
255	if valid && validationError == nil {
256		haveStr, _ = ComputeClassLoaderContext(m)
257	}
258
259	// Test that validation is successful (all paths are known).
260	t.Run("validate", func(t *testing.T) {
261		if !(valid && validationError == nil) {
262			t.Errorf("invalid class loader context")
263		}
264	})
265
266	// Test that class loader context structure is correct.
267	t.Run("string", func(t *testing.T) {
268		wantStr := " --host-context-for-sdk 30 PCL[out/soong/c.jar]" +
269			" --target-context-for-sdk 30 PCL[/system/c.jar]" +
270			" --host-context-for-sdk 29 PCL[out/soong/b.jar]" +
271			" --target-context-for-sdk 29 PCL[/system/b.jar]" +
272			" --host-context-for-sdk 28 PCL[out/soong/a.jar]" +
273			" --target-context-for-sdk 28 PCL[/system/a.jar]" +
274			" --host-context-for-sdk any PCL[out/soong/d.jar]" +
275			" --target-context-for-sdk any PCL[/system/d.jar]"
276		if wantStr != haveStr {
277			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
278		}
279	})
280}
281
282func TestCLCMExcludeLibs(t *testing.T) {
283	ctx := testContext()
284	const optional = false
285
286	excludeLibs := func(t *testing.T, m ClassLoaderContextMap, excluded_libs ...string) ClassLoaderContextMap {
287		// Dump the CLCM before creating a new copy that excludes a specific set of libraries.
288		before := m.Dump()
289
290		// Create a new CLCM that excludes some libraries.
291		c := m.ExcludeLibs(excluded_libs)
292
293		// Make sure that the original CLCM was not changed.
294		after := m.Dump()
295		android.AssertStringEquals(t, "input CLCM modified", before, after)
296
297		return c
298	}
299
300	t.Run("exclude nothing", func(t *testing.T) {
301		m := make(ClassLoaderContextMap)
302		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
303
304		a := excludeLibs(t, m)
305
306		android.AssertStringEquals(t, "output CLCM ", `{
307  "28": [
308    {
309      "Name": "a",
310      "Optional": false,
311      "Host": "out/soong/a.jar",
312      "Device": "/system/a.jar",
313      "Subcontexts": []
314    }
315  ]
316}`, a.Dump())
317	})
318
319	t.Run("one item from list", func(t *testing.T) {
320		m := make(ClassLoaderContextMap)
321		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
322		m.AddContext(ctx, 28, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
323
324		a := excludeLibs(t, m, "a")
325
326		expected := `{
327  "28": [
328    {
329      "Name": "b",
330      "Optional": false,
331      "Host": "out/soong/b.jar",
332      "Device": "/system/b.jar",
333      "Subcontexts": []
334    }
335  ]
336}`
337		android.AssertStringEquals(t, "output CLCM ", expected, a.Dump())
338	})
339
340	t.Run("all items from a list", func(t *testing.T) {
341		m := make(ClassLoaderContextMap)
342		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
343		m.AddContext(ctx, 28, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
344
345		a := excludeLibs(t, m, "a", "b")
346
347		android.AssertStringEquals(t, "output CLCM ", `{}`, a.Dump())
348	})
349
350	t.Run("items from a subcontext", func(t *testing.T) {
351		s := make(ClassLoaderContextMap)
352		s.AddContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
353		s.AddContext(ctx, AnySdkVersion, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
354
355		m := make(ClassLoaderContextMap)
356		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), s)
357
358		a := excludeLibs(t, m, "b")
359
360		android.AssertStringEquals(t, "output CLCM ", `{
361  "28": [
362    {
363      "Name": "a",
364      "Optional": false,
365      "Host": "out/soong/a.jar",
366      "Device": "/system/a.jar",
367      "Subcontexts": [
368        {
369          "Name": "c",
370          "Optional": false,
371          "Host": "out/soong/c.jar",
372          "Device": "/system/c.jar",
373          "Subcontexts": []
374        }
375      ]
376    }
377  ]
378}`, a.Dump())
379	})
380}
381
382// Test that CLC is correctly serialized to JSON.
383func TestCLCtoJSON(t *testing.T) {
384	ctx := testContext()
385	optional := false
386	m := make(ClassLoaderContextMap)
387	m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
388	m.AddContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
389	android.AssertStringEquals(t, "output CLCM ", `{
390  "28": [
391    {
392      "Name": "a",
393      "Optional": false,
394      "Host": "out/soong/a.jar",
395      "Device": "/system/a.jar",
396      "Subcontexts": []
397    }
398  ],
399  "any": [
400    {
401      "Name": "b",
402      "Optional": false,
403      "Host": "out/soong/b.jar",
404      "Device": "/system/b.jar",
405      "Subcontexts": []
406    }
407  ]
408}`, m.Dump())
409}
410
411func checkError(t *testing.T, have error, want string) {
412	if have == nil {
413		t.Errorf("\nwant error: '%s'\nhave: none", want)
414	} else if msg := have.Error(); !strings.HasPrefix(msg, want) {
415		t.Errorf("\nwant error: '%s'\nhave error: '%s'\n", want, msg)
416	}
417}
418
419func testContext() android.ModuleInstallPathContext {
420	config := android.TestConfig("out", nil, "", nil)
421	return android.ModuleInstallPathContextForTesting(config)
422}
423
424func buildPath(ctx android.PathContext, lib string) android.Path {
425	return android.PathForOutput(ctx, lib+".jar")
426}
427
428func installPath(ctx android.ModuleInstallPathContext, lib string) android.InstallPath {
429	return android.PathForModuleInstall(ctx, lib+".jar")
430}
431