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