1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "derive_classpath.h"
18 
19 #include <android-base/file.h>
20 #include <android-base/logging.h>
21 #include <android-base/properties.h>
22 #include <android-base/stringprintf.h>
23 #include <android-base/strings.h>
24 #include <android-modules-utils/sdk_level.h>
25 #include <android/api-level.h>
26 #include <gtest/gtest.h>
27 #include <stdlib.h>
28 #include <sys/mman.h>
29 #include <sys/stat.h>
30 
31 #include <cstdlib>
32 #include <string_view>
33 
34 #include "android-base/unique_fd.h"
35 #include "packages/modules/common/proto/classpaths.pb.h"
36 
37 namespace android {
38 namespace derive_classpath {
39 namespace {
40 
41 static const std::string kFrameworkJarFilepath = "/system/framework/framework.jar";
42 static const std::string kLibartJarFilepath = "/apex/com.android.art/javalib/core-libart.jar";
43 static const std::string kSdkExtensionsJarFilepath =
44     "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar";
45 static const std::string kServicesJarFilepath = "/system/framework/services.jar";
46 
47 // The fixture for testing derive_classpath.
48 class DeriveClasspathTest : public ::testing::Test {
49  protected:
~DeriveClasspathTest()50   ~DeriveClasspathTest() override {
51     // Not really needed, as a test device will re-generate a proper classpath on reboot,
52     // but it's better to leave it in a clean state after a test.
53     GenerateClasspathExports(default_args_);
54   }
55 
working_dir()56   const std::string working_dir() { return std::string(temp_dir_.path); }
57 
58   // Parses the generated classpath exports file and returns each line individually.
ParseExportsFile(const char * file="/data/system/environ/classpath")59   std::vector<std::string> ParseExportsFile(const char* file = "/data/system/environ/classpath") {
60     std::string contents;
61     EXPECT_TRUE(android::base::ReadFileToString(file, &contents, /*follow_symlinks=*/true));
62     return android::base::Split(contents, "\n");
63   }
64 
SplitClasspathExportLine(const std::string & line)65   std::vector<std::string> SplitClasspathExportLine(const std::string& line) {
66     std::vector<std::string> contents = android::base::Split(line, " ");
67     // Export lines are expected to be structured as `export <name> <value>`.
68     EXPECT_EQ(3, contents.size());
69     EXPECT_EQ("export", contents[0]);
70     return contents;
71   }
72 
73   // Checks the order of the jars in a given classpath.
74   // Instead of doing a full order check, it assumes the jars are grouped by partition and checks
75   // that partitions come in order of the `prefixes` that is given.
CheckClasspathGroupOrder(const std::string classpath,const std::vector<std::string> prefixes)76   void CheckClasspathGroupOrder(const std::string classpath,
77                                 const std::vector<std::string> prefixes) {
78     ASSERT_NE(0, prefixes.size());
79     ASSERT_NE(0, classpath.size());
80 
81     auto jars = android::base::Split(classpath, ":");
82 
83     auto prefix = prefixes.begin();
84     auto jar = jars.begin();
85     for (; jar != jars.end() && prefix != prefixes.end(); ++jar) {
86       if (*jar == "/apex/com.android.i18n/javalib/core-icu4j.jar") {
87         // core-icu4j.jar is special and is out of order in BOOTCLASSPATH;
88         // ignore it when checking for general order
89         continue;
90       }
91       if (!android::base::StartsWith(*jar, *prefix)) {
92         ++prefix;
93       }
94     }
95     EXPECT_NE(prefix, prefixes.end());
96     // All jars have been iterated over, thus they all have valid prefixes
97     EXPECT_EQ(jar, jars.end());
98   }
99 
WriteConfig(const ExportedClasspathsJars & exported_jars,const std::string & path)100   void WriteConfig(const ExportedClasspathsJars& exported_jars, const std::string& path) {
101     std::string fragment_path = working_dir() + path;
102     std::string buf;
103     exported_jars.SerializeToString(&buf);
104     std::string cmd("mkdir -p " + android::base::Dirname(fragment_path));
105     ASSERT_EQ(0, system(cmd.c_str()));
106     ASSERT_TRUE(android::base::WriteStringToFile(buf, fragment_path, true));
107   }
108 
AddJarToClasspath(const std::string & partition,const std::string & jar_filepath,Classpath classpath)109   void AddJarToClasspath(const std::string& partition, const std::string& jar_filepath,
110                          Classpath classpath) {
111     ExportedClasspathsJars exported_jars;
112     Jar* jar = exported_jars.add_jars();
113     jar->set_path(jar_filepath);
114     jar->set_classpath(classpath);
115 
116     std::string basename = Classpath_Name(classpath) + ".pb";
117     std::transform(basename.begin(), basename.end(), basename.begin(),
118                    [](unsigned char c) { return std::tolower(c); });
119 
120     WriteConfig(exported_jars, partition + "/etc/classpaths/" + basename);
121   }
122 
123   const TemporaryDir temp_dir_;
124 
125   const Args default_args_ = {
126       .output_path = kGeneratedClasspathExportsFilepath,
127   };
128 
129   const Args default_args_with_test_dir_ = {
130       .output_path = kGeneratedClasspathExportsFilepath,
131       .glob_pattern_prefix = temp_dir_.path,
132   };
133 };
134 
135 using DeriveClasspathDeathTest = DeriveClasspathTest;
136 
137 // Check only known *CLASSPATH variables are exported.
TEST_F(DeriveClasspathTest,DefaultNoUnknownClasspaths)138 TEST_F(DeriveClasspathTest, DefaultNoUnknownClasspaths) {
139   // Re-generate default on device classpaths
140   GenerateClasspathExports(default_args_);
141 
142   const std::vector<std::string> exportLines = ParseExportsFile();
143   // The first four lines are tested below.
144   for (int i = 4; i < exportLines.size(); i++) {
145     EXPECT_EQ(exportLines[i], "");
146   }
147 }
148 
149 // Test that all variables are properly generated.
TEST_F(DeriveClasspathTest,AllVariables)150 TEST_F(DeriveClasspathTest, AllVariables) {
151   ExportedClasspathsJars exported_jars;
152   Jar* jar = exported_jars.add_jars();
153   jar->set_path("/apex/com.android.foo/javalib/foo");
154   jar->set_classpath(BOOTCLASSPATH);
155   jar = exported_jars.add_jars();
156   jar->set_path("/apex/com.android.bar/javalib/bar");
157   jar->set_classpath(DEX2OATBOOTCLASSPATH);
158   WriteConfig(exported_jars, "/system/etc/classpaths/bootclasspath.pb");
159 
160   exported_jars.clear_jars();
161   jar = exported_jars.add_jars();
162   jar->set_path("/apex/com.android.baz/javalib/baz");
163   jar->set_classpath(SYSTEMSERVERCLASSPATH);
164   jar = exported_jars.add_jars();
165   jar->set_path("/apex/com.android.qux/javalib/qux");
166   jar->set_classpath(STANDALONE_SYSTEMSERVER_JARS);
167   WriteConfig(exported_jars, "/system/etc/classpaths/systemserverclasspath.pb");
168 
169   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
170 
171   const std::vector<std::string> exportLines = ParseExportsFile();
172   std::vector<std::string> splitExportLine;
173 
174   splitExportLine = SplitClasspathExportLine(exportLines[0]);
175   EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
176   EXPECT_EQ("/apex/com.android.foo/javalib/foo", splitExportLine[2]);
177   splitExportLine = SplitClasspathExportLine(exportLines[1]);
178   EXPECT_EQ("DEX2OATBOOTCLASSPATH", splitExportLine[1]);
179   EXPECT_EQ("/apex/com.android.bar/javalib/bar", splitExportLine[2]);
180   splitExportLine = SplitClasspathExportLine(exportLines[2]);
181   EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
182   EXPECT_EQ("/apex/com.android.baz/javalib/baz", splitExportLine[2]);
183   splitExportLine = SplitClasspathExportLine(exportLines[3]);
184   EXPECT_EQ("STANDALONE_SYSTEMSERVER_JARS", splitExportLine[1]);
185   EXPECT_EQ("/apex/com.android.qux/javalib/qux", splitExportLine[2]);
186 }
187 
188 // Test that temp directory does not pick up actual jars.
TEST_F(DeriveClasspathTest,TempConfig)189 TEST_F(DeriveClasspathTest, TempConfig) {
190   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
191   AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz",
192                     SYSTEMSERVERCLASSPATH);
193 
194   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
195 
196   const std::vector<std::string> exportLines = ParseExportsFile();
197 
198   std::vector<std::string> splitExportLine;
199 
200   splitExportLine = SplitClasspathExportLine(exportLines[0]);
201   EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
202   EXPECT_EQ("/apex/com.android.foo/javalib/foo", splitExportLine[2]);
203   splitExportLine = SplitClasspathExportLine(exportLines[2]);
204   EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
205   EXPECT_EQ("/apex/com.android.baz/javalib/baz", splitExportLine[2]);
206 }
207 
208 // Test individual modules are sorted by pathnames.
TEST_F(DeriveClasspathTest,ModulesAreSorted)209 TEST_F(DeriveClasspathTest, ModulesAreSorted) {
210   AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
211   AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
212   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
213   AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH);
214   AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH);
215 
216   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
217 
218   const std::vector<std::string> exportLines = ParseExportsFile();
219   const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[0]);
220   const std::string exportValue = splitExportLine[2];
221 
222   const std::string expectedJars(
223       "/apex/com.android.art/javalib/art"
224       ":/system/framework/jar"
225       ":/apex/com.android.bar/javalib/bar"
226       ":/apex/com.android.baz/javalib/baz"
227       ":/apex/com.android.foo/javalib/foo");
228 
229   EXPECT_EQ(expectedJars, exportValue);
230 }
231 
232 // Test we can output to custom files.
TEST_F(DeriveClasspathTest,CustomOutputLocation)233 TEST_F(DeriveClasspathTest, CustomOutputLocation) {
234   AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
235   AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
236   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
237   AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH);
238   AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH);
239 
240   android::base::unique_fd fd(memfd_create("temp_file", MFD_CLOEXEC));
241   ASSERT_TRUE(fd.ok()) << "Unable to open temp-file";
242   const std::string file_name = android::base::StringPrintf("/proc/self/fd/%d", fd.get());
243   Args args = {
244       .output_path = file_name,
245       .glob_pattern_prefix = working_dir(),
246   };
247   ASSERT_TRUE(GenerateClasspathExports(args));
248 
249   const std::vector<std::string> exportLines = ParseExportsFile(file_name.c_str());
250   const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[0]);
251   const std::string exportValue = splitExportLine[2];
252 
253   const std::string expectedJars(
254       "/apex/com.android.art/javalib/art"
255       ":/system/framework/jar"
256       ":/apex/com.android.bar/javalib/bar"
257       ":/apex/com.android.baz/javalib/baz"
258       ":/apex/com.android.foo/javalib/foo");
259 
260   EXPECT_EQ(expectedJars, exportValue);
261 }
262 
263 // Test alternative .pb for bootclasspath and systemclasspath.
TEST_F(DeriveClasspathTest,CustomInputLocation)264 TEST_F(DeriveClasspathTest, CustomInputLocation) {
265   AddJarToClasspath("/other", "/other/bcp-jar", BOOTCLASSPATH);
266   AddJarToClasspath("/other", "/other/systemserver-jar", SYSTEMSERVERCLASSPATH);
267   AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
268   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
269   AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz",
270                     SYSTEMSERVERCLASSPATH);
271 
272   Args args = default_args_with_test_dir_;
273   args.system_bootclasspath_fragment = "/other/etc/classpaths/bootclasspath.pb";
274   args.system_systemserverclasspath_fragment = "/other/etc/classpaths/systemserverclasspath.pb";
275 
276   ASSERT_TRUE(GenerateClasspathExports(args));
277 
278   const std::vector<std::string> exportLines = ParseExportsFile();
279 
280   std::vector<std::string> splitExportLine;
281   splitExportLine = SplitClasspathExportLine(exportLines[0]);
282   EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
283   const std::string expectedBcpJars(
284       "/apex/com.android.art/javalib/art"
285       ":/other/bcp-jar"
286       ":/apex/com.android.foo/javalib/foo");
287   EXPECT_EQ(expectedBcpJars, splitExportLine[2]);
288 
289   splitExportLine = SplitClasspathExportLine(exportLines[2]);
290   EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
291   const std::string expectedSystemServerJars(
292       "/other/systemserver-jar"
293       ":/apex/com.android.baz/javalib/baz");
294   EXPECT_EQ(expectedSystemServerJars, splitExportLine[2]);
295 }
296 
297 // Test output location that can't be written to.
TEST_F(DeriveClasspathTest,NonWriteableOutputLocation)298 TEST_F(DeriveClasspathTest, NonWriteableOutputLocation) {
299   AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
300   AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
301 
302   Args args = {
303       .output_path = "/system/non_writable_path",
304       .glob_pattern_prefix = working_dir(),
305   };
306   ASSERT_FALSE(GenerateClasspathExports(args));
307 }
308 
TEST_F(DeriveClasspathTest,ScanOnlySpecificDirectories)309 TEST_F(DeriveClasspathTest, ScanOnlySpecificDirectories) {
310   AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
311   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
312   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/sys",
313                     SYSTEMSERVERCLASSPATH);
314   AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH);
315   AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH);
316 
317   auto args_with_scan_dirs = default_args_with_test_dir_;
318   args_with_scan_dirs.scan_dirs.push_back("/apex/com.android.foo");
319   args_with_scan_dirs.scan_dirs.push_back("/apex/com.android.bar");
320   ASSERT_TRUE(GenerateClasspathExports(args_with_scan_dirs));
321 
322   const std::vector<std::string> exportLines = ParseExportsFile();
323 
324   std::vector<std::string> splitExportLine;
325 
326   splitExportLine = SplitClasspathExportLine(exportLines[0]);
327   EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
328   // Not sorted. Maintains the ordering provided in scan_dirs
329   const std::string expectedJars(
330       "/apex/com.android.foo/javalib/foo"
331       ":/apex/com.android.bar/javalib/bar");
332   EXPECT_EQ(expectedJars, splitExportLine[2]);
333   splitExportLine = SplitClasspathExportLine(exportLines[2]);
334   EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
335   EXPECT_EQ("/apex/com.android.foo/javalib/sys", splitExportLine[2]);
336 }
337 
338 // Test apexes only export their own jars.
TEST_F(DeriveClasspathDeathTest,ApexJarsBelongToApex)339 TEST_F(DeriveClasspathDeathTest, ApexJarsBelongToApex) {
340   // EXPECT_DEATH expects error messages in stderr, log there
341   android::base::SetLogger(android::base::StderrLogger);
342 
343   AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
344   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
345 
346   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
347   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
348 
349   AddJarToClasspath("/apex/com.android.bar@12345.tmp", "/apex/com.android.bar/javalib/bar",
350                     BOOTCLASSPATH);
351   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
352 
353   AddJarToClasspath("/apex/com.android.baz@12345", "/apex/this/path/is/skipped", BOOTCLASSPATH);
354   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
355 
356   AddJarToClasspath("/apex/com.android.bar", "/apex/wrong/path/bar", BOOTCLASSPATH);
357   EXPECT_DEATH(GenerateClasspathExports(default_args_with_test_dir_),
358                "must not export a jar.*wrong/path/bar");
359 }
360 
361 // Test only bind mounted apexes are skipped
TEST_F(DeriveClasspathTest,OnlyBindMountedApexIsSkipped)362 TEST_F(DeriveClasspathTest, OnlyBindMountedApexIsSkipped) {
363   AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
364   // Normal APEX with format: /apex/<module-name>/*
365   AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
366   // Bind mounted APEX with format: /apex/<module-name>@<version>/*
367   AddJarToClasspath("/apex/com.android.bar@123", "/apex/com.android.bar/javalib/bar",
368                     BOOTCLASSPATH);
369   // Temp mounted APEX with format: /apex/<module-name>@<version>.tmp/*
370   AddJarToClasspath("/apex/com.android.baz@123.tmp", "/apex/com.android.baz/javalib/baz",
371                     BOOTCLASSPATH);
372 
373   ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
374 
375   const std::vector<std::string> exportLines = ParseExportsFile();
376 
377   std::vector<std::string> splitExportLine;
378 
379   splitExportLine = SplitClasspathExportLine(exportLines[0]);
380   EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
381   const std::string expectedJars(
382       "/system/framework/jar"
383       ":/apex/com.android.baz/javalib/baz"
384       ":/apex/com.android.foo/javalib/foo");
385   EXPECT_EQ(expectedJars, splitExportLine[2]);
386 }
387 
388 // Test classpath fragments export jars for themselves.
TEST_F(DeriveClasspathDeathTest,WrongClasspathInFragments)389 TEST_F(DeriveClasspathDeathTest, WrongClasspathInFragments) {
390   // Valid configs
391   AddJarToClasspath("/system", "/system/framework/framework-jar", BOOTCLASSPATH);
392   AddJarToClasspath("/system", "/system/framework/service-jar", SYSTEMSERVERCLASSPATH);
393 
394   // Manually create an invalid config with both BCP and SSCP jars...
395   ExportedClasspathsJars exported_jars;
396   Jar* jar = exported_jars.add_jars();
397   jar->set_path("/apex/com.android.foo/javalib/foo");
398   jar->set_classpath(BOOTCLASSPATH);
399   // note that DEX2OATBOOTCLASSPATH and BOOTCLASSPATH jars are expected to be in the same config
400   jar = exported_jars.add_jars();
401   jar->set_path("/apex/com.android.foo/javalib/foo");
402   jar->set_classpath(DEX2OATBOOTCLASSPATH);
403   jar = exported_jars.add_jars();
404   jar->set_path("/apex/com.android.foo/javalib/service-foo");
405   jar->set_classpath(SYSTEMSERVERCLASSPATH);
406 
407   // ...and write this config to bootclasspath.pb
408   WriteConfig(exported_jars, "/apex/com.android.foo/etc/classpaths/bootclasspath.pb");
409 
410   EXPECT_DEATH(GenerateClasspathExports(default_args_with_test_dir_),
411                "must not export a jar for SYSTEMSERVERCLASSPATH");
412 }
413 
TEST_F(DeriveClasspathDeathTest,CurrentSdkVersion)414 TEST_F(DeriveClasspathDeathTest, CurrentSdkVersion) {
415   if (android_get_device_api_level() < __ANDROID_API_S__) {
416     GTEST_SKIP();
417   }
418 
419   ExportedClasspathsJars exported_jars;
420   Jar* jar = exported_jars.add_jars();
421   jar->set_path("/apex/com.android.foo/javalib/minsdkcurrent");
422   jar->set_min_sdk_version("current");
423   jar->set_classpath(SYSTEMSERVERCLASSPATH);
424   WriteConfig(exported_jars, "/apex/com.android.foo/etc/classpaths/systemserverclasspath.pb");
425 
426   EXPECT_DEATH(GenerateClasspathExports(default_args_with_test_dir_), "no conversion");
427 }
428 
429 // Test jars with different sdk versions.
TEST_F(DeriveClasspathTest,SdkVersionsAreRespected)430 TEST_F(DeriveClasspathTest, SdkVersionsAreRespected) {
431   if (android_get_device_api_level() < __ANDROID_API_S__) {
432     GTEST_SKIP();
433   }
434 
435   // List of jars expected to be in SYSTEMSERVERCLASSPATH
436   std::vector<std::string> expected_jars;
437 
438   // Add an unbounded jar
439   AddJarToClasspath("/system", "/system/framework/unbounded", SYSTEMSERVERCLASSPATH);
440   expected_jars.push_back("/system/framework/unbounded");
441 
442   // Manually create a config with jars that set sdk versions...
443   ExportedClasspathsJars exported_jars;
444 
445   // known released versions:
446   Jar* jar = exported_jars.add_jars();
447   jar->set_path("/apex/com.android.foo/javalib/minsdk30");
448   jar->set_min_sdk_version(std::to_string(__ANDROID_API_R__));
449   jar->set_classpath(SYSTEMSERVERCLASSPATH);
450   expected_jars.push_back("/apex/com.android.foo/javalib/minsdk30");
451   jar = exported_jars.add_jars();
452   jar->set_path("/apex/com.android.foo/javalib/maxsdk30");
453   jar->set_max_sdk_version(std::to_string(__ANDROID_API_R__));
454   jar->set_classpath(SYSTEMSERVERCLASSPATH);
455 
456   // Device's reported version:
457   jar = exported_jars.add_jars();
458   jar->set_path("/apex/com.android.foo/javalib/minsdklatest");
459   jar->set_min_sdk_version(std::to_string(android_get_device_api_level()));
460   jar->set_classpath(SYSTEMSERVERCLASSPATH);
461   expected_jars.push_back("/apex/com.android.foo/javalib/minsdklatest");
462   jar = exported_jars.add_jars();
463   jar->set_path("/apex/com.android.foo/javalib/maxsdklatest");
464   jar->set_max_sdk_version(std::to_string(android_get_device_api_level()));
465   jar->set_classpath(SYSTEMSERVERCLASSPATH);
466   if ("REL" == android::base::GetProperty("ro.build.version.codename", "")) {
467     expected_jars.push_back("/apex/com.android.foo/javalib/maxsdklatest");
468   }
469 
470   // unknown SDK_INT+1 version
471   jar = exported_jars.add_jars();
472   jar->set_path("/apex/com.android.foo/javalib/minsdk_plus1");
473   jar->set_min_sdk_version(std::to_string(android_get_device_api_level() + 1));
474   jar->set_classpath(SYSTEMSERVERCLASSPATH);
475   jar = exported_jars.add_jars();
476   jar->set_path("/apex/com.android.foo/javalib/maxsdk_plus1");
477   jar->set_max_sdk_version(std::to_string(android_get_device_api_level() + 1));
478   jar->set_classpath(SYSTEMSERVERCLASSPATH);
479   expected_jars.push_back("/apex/com.android.foo/javalib/maxsdk_plus1");
480 
481   // known min_sdk_version and future max_sdk_version
482   jar = exported_jars.add_jars();
483   jar->set_path("/apex/com.android.foo/javalib/minsdk30maxsdk10000");
484   jar->set_min_sdk_version(std::to_string(__ANDROID_API_R__));
485   jar->set_max_sdk_version(std::to_string(android_get_device_api_level() + 1));
486   jar->set_classpath(SYSTEMSERVERCLASSPATH);
487   expected_jars.push_back("/apex/com.android.foo/javalib/minsdk30maxsdk10000");
488 
489   // codename
490   if ("REL" != android::base::GetProperty("ro.build.version.codename", "")) {
491     jar = exported_jars.add_jars();
492     jar->set_path("/apex/com.android.foo/javalib/minsdkS");
493     jar->set_min_sdk_version("S");
494     jar->set_classpath(SYSTEMSERVERCLASSPATH);
495     expected_jars.push_back("/apex/com.android.foo/javalib/minsdkS");
496 
497     jar = exported_jars.add_jars();
498     jar->set_path("/apex/com.android.foo/javalib/minsdkSv2");
499     jar->set_min_sdk_version("Sv2");
500     jar->set_classpath(SYSTEMSERVERCLASSPATH);
501     expected_jars.push_back("/apex/com.android.foo/javalib/minsdkSv2");
502 
503     jar = exported_jars.add_jars();
504     jar->set_path("/apex/com.android.foo/javalib/minsdkTiramisu");
505     jar->set_min_sdk_version("Tiramisu");
506     jar->set_classpath(SYSTEMSERVERCLASSPATH);
507     expected_jars.push_back("/apex/com.android.foo/javalib/minsdkTiramisu");
508 
509     jar = exported_jars.add_jars();
510     jar->set_path("/apex/com.android.foo/javalib/maxsdkS");
511     jar->set_max_sdk_version("S");
512     jar->set_classpath(SYSTEMSERVERCLASSPATH);
513 
514     jar = exported_jars.add_jars();
515     jar->set_path("/apex/com.android.foo/javalib/maxsdkSv2");
516     jar->set_max_sdk_version("Sv2");
517     jar->set_classpath(SYSTEMSERVERCLASSPATH);
518 
519     jar = exported_jars.add_jars();
520     jar->set_path("/apex/com.android.foo/javalib/maxsdkZFutureSdkVersion");
521     jar->set_max_sdk_version("ZFutureSdkVersion");
522     jar->set_classpath(SYSTEMSERVERCLASSPATH);
523     expected_jars.push_back("/apex/com.android.foo/javalib/maxsdkZFutureSdkVersion");
524   }
525 
526   // ...and write this config to systemserverclasspath.pb
527   WriteConfig(exported_jars, "/apex/com.android.foo/etc/classpaths/systemserverclasspath.pb");
528 
529   // Generate and parse SYSTEMSERVERCLASSPATH
530   GenerateClasspathExports(default_args_with_test_dir_);
531   const std::vector<std::string> exportLines = ParseExportsFile();
532   const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[2]);
533   const std::string exportValue = splitExportLine[2];
534 
535   EXPECT_EQ(android::base::Join(expected_jars, ":"), exportValue);
536 }
537 
538 }  // namespace
539 }  // namespace derive_classpath
540 }  // namespace android
541 
main(int argc,char ** argv)542 int main(int argc, char** argv) {
543   ::testing::InitGoogleTest(&argc, argv);
544   // Required for EXPECT_DEATH to work correctly
545   android::base::SetLogger(android::base::StderrLogger);
546   return RUN_ALL_TESTS();
547 }
548