class_loader_context_test.go 15 KB

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