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