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	m := make(ClassLoaderContextMap)
53
54	m.AddContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
55	m.AddContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
56	m.AddContext(ctx, AnySdkVersion, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
57
58	// Add some libraries with nested subcontexts.
59
60	m1 := make(ClassLoaderContextMap)
61	m1.AddContext(ctx, AnySdkVersion, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"), nil)
62	m1.AddContext(ctx, AnySdkVersion, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"), nil)
63
64	m2 := make(ClassLoaderContextMap)
65	m2.AddContext(ctx, AnySdkVersion, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"), nil)
66	m2.AddContext(ctx, AnySdkVersion, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"), nil)
67	m2.AddContext(ctx, AnySdkVersion, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
68
69	m3 := make(ClassLoaderContextMap)
70	m3.AddContext(ctx, AnySdkVersion, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"), nil)
71	m3.AddContext(ctx, AnySdkVersion, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"), nil)
72
73	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), m2)
74	// When the same library is both in conditional and unconditional context, it should be removed
75	// from conditional context.
76	m.AddContext(ctx, 42, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
77	m.AddContext(ctx, AnySdkVersion, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
78
79	// Merge map with implicit root library that is among toplevel contexts => does nothing.
80	m.AddContextMap(m1, "c")
81	// Merge map with implicit root library that is not among toplevel contexts => all subcontexts
82	// of the other map are added as toplevel contexts.
83	m.AddContextMap(m3, "m_g")
84
85	// Compatibility libraries with unknown install paths get default paths.
86	m.AddContext(ctx, 29, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil, nil)
87	m.AddContext(ctx, 29, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil, nil)
88
89	// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
90	// needed as a compatibility library if "android.test.runner" is in CLC as well.
91	m.AddContext(ctx, 30, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil, nil)
92
93	valid, validationError := validateClassLoaderContext(m)
94
95	fixClassLoaderContext(m)
96
97	var haveStr string
98	var havePaths android.Paths
99	var haveUsesLibs []string
100	if valid && validationError == nil {
101		haveStr, havePaths = ComputeClassLoaderContext(m)
102		haveUsesLibs = m.UsesLibs()
103	}
104
105	// Test that validation is successful (all paths are known).
106	t.Run("validate", func(t *testing.T) {
107		if !(valid && validationError == nil) {
108			t.Errorf("invalid class loader context")
109		}
110	})
111
112	// Test that class loader context structure is correct.
113	t.Run("string", func(t *testing.T) {
114		wantStr := " --host-context-for-sdk 29 " +
115			"PCL[out/" + AndroidHidlManager + ".jar]#" +
116			"PCL[out/" + AndroidHidlBase + ".jar]" +
117			" --target-context-for-sdk 29 " +
118			"PCL[/system/framework/" + AndroidHidlManager + ".jar]#" +
119			"PCL[/system/framework/" + AndroidHidlBase + ".jar]" +
120			" --host-context-for-sdk any " +
121			"PCL[out/a.jar]#PCL[out/b.jar]#PCL[out/c.jar]#PCL[out/d.jar]" +
122			"{PCL[out/a2.jar]#PCL[out/b2.jar]#PCL[out/c2.jar]" +
123			"{PCL[out/a1.jar]#PCL[out/b1.jar]}}#" +
124			"PCL[out/f.jar]#PCL[out/a3.jar]#PCL[out/b3.jar]" +
125			" --target-context-for-sdk any " +
126			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]" +
127			"{PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]" +
128			"{PCL[/system/a1.jar]#PCL[/system/b1.jar]}}#" +
129			"PCL[/system/f.jar]#PCL[/system/a3.jar]#PCL[/system/b3.jar]"
130		if wantStr != haveStr {
131			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
132		}
133	})
134
135	// Test that all expected build paths are gathered.
136	t.Run("paths", func(t *testing.T) {
137		wantPaths := []string{
138			"out/android.hidl.manager-V1.0-java.jar", "out/android.hidl.base-V1.0-java.jar",
139			"out/a.jar", "out/b.jar", "out/c.jar", "out/d.jar",
140			"out/a2.jar", "out/b2.jar", "out/c2.jar",
141			"out/a1.jar", "out/b1.jar",
142			"out/f.jar", "out/a3.jar", "out/b3.jar",
143		}
144		if !reflect.DeepEqual(wantPaths, havePaths.Strings()) {
145			t.Errorf("\nwant paths: %s\nhave paths: %s", wantPaths, havePaths)
146		}
147	})
148
149	// Test for libraries that are added by the manifest_fixer.
150	t.Run("uses libs", func(t *testing.T) {
151		wantUsesLibs := []string{"a", "b", "c", "d", "f", "a3", "b3"}
152		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
153			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
154		}
155	})
156}
157
158func TestCLCJson(t *testing.T) {
159	ctx := testContext()
160	m := make(ClassLoaderContextMap)
161	m.AddContext(ctx, 28, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
162	m.AddContext(ctx, 29, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
163	m.AddContext(ctx, 30, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
164	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), nil)
165	jsonCLC := toJsonClassLoaderContext(m)
166	restored := fromJsonClassLoaderContext(ctx, jsonCLC)
167	android.AssertIntEquals(t, "The size of the maps should be the same.", len(m), len(restored))
168	for k := range m {
169		a, _ := m[k]
170		b, ok := restored[k]
171		android.AssertBoolEquals(t, "The both maps should have the same keys.", ok, true)
172		android.AssertIntEquals(t, "The size of the elements should be the same.", len(a), len(b))
173		for i, elemA := range a {
174			before := fmt.Sprintf("%v", *elemA)
175			after := fmt.Sprintf("%v", *b[i])
176			android.AssertStringEquals(t, "The content should be the same.", before, after)
177		}
178	}
179}
180
181// Test that unknown library paths cause a validation error.
182func testCLCUnknownPath(t *testing.T, whichPath string) {
183	ctx := testContext()
184
185	m := make(ClassLoaderContextMap)
186	if whichPath == "build" {
187		m.AddContext(ctx, AnySdkVersion, "a", nil, nil, nil)
188	} else {
189		m.AddContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), nil, nil)
190	}
191
192	// The library should be added to <uses-library> tags by the manifest_fixer.
193	t.Run("uses libs", func(t *testing.T) {
194		haveUsesLibs := m.UsesLibs()
195		wantUsesLibs := []string{"a"}
196		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
197			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
198		}
199	})
200
201	// But CLC cannot be constructed: there is a validation error.
202	_, err := validateClassLoaderContext(m)
203	checkError(t, err, fmt.Sprintf("invalid %s path for <uses-library> \"a\"", whichPath))
204}
205
206// Test that unknown build path is an error.
207func TestCLCUnknownBuildPath(t *testing.T) {
208	testCLCUnknownPath(t, "build")
209}
210
211// Test that unknown install path is an error.
212func TestCLCUnknownInstallPath(t *testing.T) {
213	testCLCUnknownPath(t, "install")
214}
215
216// An attempt to add conditional nested subcontext should fail.
217func TestCLCNestedConditional(t *testing.T) {
218	ctx := testContext()
219	m1 := make(ClassLoaderContextMap)
220	m1.AddContext(ctx, 42, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
221	m := make(ClassLoaderContextMap)
222	err := m.addContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), m1)
223	checkError(t, err, "nested class loader context shouldn't have conditional part")
224}
225
226// Test for SDK version order in conditional CLC: no matter in what order the libraries are added,
227// they end up in the order that agrees with PackageManager.
228func TestCLCSdkVersionOrder(t *testing.T) {
229	ctx := testContext()
230	m := make(ClassLoaderContextMap)
231	m.AddContext(ctx, 28, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
232	m.AddContext(ctx, 29, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
233	m.AddContext(ctx, 30, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
234	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), nil)
235
236	valid, validationError := validateClassLoaderContext(m)
237
238	fixClassLoaderContext(m)
239
240	var haveStr string
241	if valid && validationError == nil {
242		haveStr, _ = ComputeClassLoaderContext(m)
243	}
244
245	// Test that validation is successful (all paths are known).
246	t.Run("validate", func(t *testing.T) {
247		if !(valid && validationError == nil) {
248			t.Errorf("invalid class loader context")
249		}
250	})
251
252	// Test that class loader context structure is correct.
253	t.Run("string", func(t *testing.T) {
254		wantStr := " --host-context-for-sdk 30 PCL[out/c.jar]" +
255			" --target-context-for-sdk 30 PCL[/system/c.jar]" +
256			" --host-context-for-sdk 29 PCL[out/b.jar]" +
257			" --target-context-for-sdk 29 PCL[/system/b.jar]" +
258			" --host-context-for-sdk 28 PCL[out/a.jar]" +
259			" --target-context-for-sdk 28 PCL[/system/a.jar]" +
260			" --host-context-for-sdk any PCL[out/d.jar]" +
261			" --target-context-for-sdk any PCL[/system/d.jar]"
262		if wantStr != haveStr {
263			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
264		}
265	})
266}
267
268func checkError(t *testing.T, have error, want string) {
269	if have == nil {
270		t.Errorf("\nwant error: '%s'\nhave: none", want)
271	} else if msg := have.Error(); !strings.HasPrefix(msg, want) {
272		t.Errorf("\nwant error: '%s'\nhave error: '%s'\n", want, msg)
273	}
274}
275
276func testContext() android.ModuleInstallPathContext {
277	config := android.TestConfig("out", nil, "", nil)
278	return android.ModuleInstallPathContextForTesting(config)
279}
280
281func buildPath(ctx android.PathContext, lib string) android.Path {
282	return android.PathForOutput(ctx, lib+".jar")
283}
284
285func installPath(ctx android.ModuleInstallPathContext, lib string) android.InstallPath {
286	return android.PathForModuleInstall(ctx, lib+".jar")
287}
288