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