blob: b2338253917dd5ac9444c9e4d79256ea35155e00 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "derive_classpath.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-modules-utils/sdk_level.h>
#include <android/api-level.h>
#include <gtest/gtest.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <cstdlib>
#include <string_view>
#include "android-base/unique_fd.h"
#include "packages/modules/common/proto/classpaths.pb.h"
namespace android {
namespace derive_classpath {
namespace {
static const std::string kFrameworkJarFilepath = "/system/framework/framework.jar";
static const std::string kLibartJarFilepath = "/apex/com.android.art/javalib/core-libart.jar";
static const std::string kSdkExtensionsJarFilepath =
"/apex/com.android.sdkext/javalib/framework-sdkextensions.jar";
static const std::string kServicesJarFilepath = "/system/framework/services.jar";
// The fixture for testing derive_classpath.
class DeriveClasspathTest : public ::testing::Test {
protected:
~DeriveClasspathTest() override {
// Not really needed, as a test device will re-generate a proper classpath on reboot,
// but it's better to leave it in a clean state after a test.
GenerateClasspathExports(default_args_);
}
const std::string working_dir() { return std::string(temp_dir_.path); }
// Parses the generated classpath exports file and returns each line individually.
std::vector<std::string> ParseExportsFile(const char* file = "/data/system/environ/classpath") {
std::string contents;
EXPECT_TRUE(android::base::ReadFileToString(file, &contents, /*follow_symlinks=*/true));
return android::base::Split(contents, "\n");
}
std::vector<std::string> SplitClasspathExportLine(const std::string& line) {
std::vector<std::string> contents = android::base::Split(line, " ");
// Export lines are expected to be structured as `export <name> <value>`.
EXPECT_EQ(3, contents.size());
EXPECT_EQ("export", contents[0]);
return contents;
}
// Checks the order of the jars in a given classpath.
// Instead of doing a full order check, it assumes the jars are grouped by partition and checks
// that partitions come in order of the `prefixes` that is given.
void CheckClasspathGroupOrder(const std::string classpath,
const std::vector<std::string> prefixes) {
ASSERT_NE(0, prefixes.size());
ASSERT_NE(0, classpath.size());
auto jars = android::base::Split(classpath, ":");
auto prefix = prefixes.begin();
auto jar = jars.begin();
for (; jar != jars.end() && prefix != prefixes.end(); ++jar) {
if (*jar == "/apex/com.android.i18n/javalib/core-icu4j.jar") {
// core-icu4j.jar is special and is out of order in BOOTCLASSPATH;
// ignore it when checking for general order
continue;
}
if (!android::base::StartsWith(*jar, *prefix)) {
++prefix;
}
}
EXPECT_NE(prefix, prefixes.end());
// All jars have been iterated over, thus they all have valid prefixes
EXPECT_EQ(jar, jars.end());
}
void WriteConfig(const ExportedClasspathsJars& exported_jars, const std::string& path) {
std::string fragment_path = working_dir() + path;
std::string buf;
exported_jars.SerializeToString(&buf);
std::string cmd("mkdir -p " + android::base::Dirname(fragment_path));
ASSERT_EQ(0, system(cmd.c_str()));
ASSERT_TRUE(android::base::WriteStringToFile(buf, fragment_path, true));
}
void AddJarToClasspath(const std::string& partition, const std::string& jar_filepath,
Classpath classpath) {
ExportedClasspathsJars exported_jars;
Jar* jar = exported_jars.add_jars();
jar->set_path(jar_filepath);
jar->set_classpath(classpath);
std::string basename = Classpath_Name(classpath) + ".pb";
std::transform(basename.begin(), basename.end(), basename.begin(),
[](unsigned char c) { return std::tolower(c); });
WriteConfig(exported_jars, partition + "/etc/classpaths/" + basename);
}
const TemporaryDir temp_dir_;
const Args default_args_ = {
.output_path = kGeneratedClasspathExportsFilepath,
};
const Args default_args_with_test_dir_ = {
.output_path = kGeneratedClasspathExportsFilepath,
.glob_pattern_prefix = temp_dir_.path,
};
};
using DeriveClasspathDeathTest = DeriveClasspathTest;
// Check only known *CLASSPATH variables are exported.
TEST_F(DeriveClasspathTest, DefaultNoUnknownClasspaths) {
// Re-generate default on device classpaths
GenerateClasspathExports(default_args_);
const std::vector<std::string> exportLines = ParseExportsFile();
// The first four lines are tested below.
for (int i = 4; i < exportLines.size(); i++) {
EXPECT_EQ(exportLines[i], "");
}
}
// Test that all variables are properly generated.
TEST_F(DeriveClasspathTest, AllVariables) {
ExportedClasspathsJars exported_jars;
Jar* jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/foo");
jar->set_classpath(BOOTCLASSPATH);
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.bar/javalib/bar");
jar->set_classpath(DEX2OATBOOTCLASSPATH);
WriteConfig(exported_jars, "/system/etc/classpaths/bootclasspath.pb");
exported_jars.clear_jars();
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.baz/javalib/baz");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.qux/javalib/qux");
jar->set_classpath(STANDALONE_SYSTEMSERVER_JARS);
WriteConfig(exported_jars, "/system/etc/classpaths/systemserverclasspath.pb");
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
const std::vector<std::string> exportLines = ParseExportsFile();
std::vector<std::string> splitExportLine;
splitExportLine = SplitClasspathExportLine(exportLines[0]);
EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
EXPECT_EQ("/apex/com.android.foo/javalib/foo", splitExportLine[2]);
splitExportLine = SplitClasspathExportLine(exportLines[1]);
EXPECT_EQ("DEX2OATBOOTCLASSPATH", splitExportLine[1]);
EXPECT_EQ("/apex/com.android.bar/javalib/bar", splitExportLine[2]);
splitExportLine = SplitClasspathExportLine(exportLines[2]);
EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
EXPECT_EQ("/apex/com.android.baz/javalib/baz", splitExportLine[2]);
splitExportLine = SplitClasspathExportLine(exportLines[3]);
EXPECT_EQ("STANDALONE_SYSTEMSERVER_JARS", splitExportLine[1]);
EXPECT_EQ("/apex/com.android.qux/javalib/qux", splitExportLine[2]);
}
// Test that temp directory does not pick up actual jars.
TEST_F(DeriveClasspathTest, TempConfig) {
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz",
SYSTEMSERVERCLASSPATH);
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
const std::vector<std::string> exportLines = ParseExportsFile();
std::vector<std::string> splitExportLine;
splitExportLine = SplitClasspathExportLine(exportLines[0]);
EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
EXPECT_EQ("/apex/com.android.foo/javalib/foo", splitExportLine[2]);
splitExportLine = SplitClasspathExportLine(exportLines[2]);
EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
EXPECT_EQ("/apex/com.android.baz/javalib/baz", splitExportLine[2]);
}
// Test individual modules are sorted by pathnames.
TEST_F(DeriveClasspathTest, ModulesAreSorted) {
AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH);
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
const std::vector<std::string> exportLines = ParseExportsFile();
const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[0]);
const std::string exportValue = splitExportLine[2];
const std::string expectedJars(
"/apex/com.android.art/javalib/art"
":/system/framework/jar"
":/apex/com.android.bar/javalib/bar"
":/apex/com.android.baz/javalib/baz"
":/apex/com.android.foo/javalib/foo");
EXPECT_EQ(expectedJars, exportValue);
}
// Test we can output to custom files.
TEST_F(DeriveClasspathTest, CustomOutputLocation) {
AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH);
android::base::unique_fd fd(memfd_create("temp_file", MFD_CLOEXEC));
ASSERT_TRUE(fd.ok()) << "Unable to open temp-file";
const std::string file_name = android::base::StringPrintf("/proc/self/fd/%d", fd.get());
Args args = {
.output_path = file_name,
.glob_pattern_prefix = working_dir(),
};
ASSERT_TRUE(GenerateClasspathExports(args));
const std::vector<std::string> exportLines = ParseExportsFile(file_name.c_str());
const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[0]);
const std::string exportValue = splitExportLine[2];
const std::string expectedJars(
"/apex/com.android.art/javalib/art"
":/system/framework/jar"
":/apex/com.android.bar/javalib/bar"
":/apex/com.android.baz/javalib/baz"
":/apex/com.android.foo/javalib/foo");
EXPECT_EQ(expectedJars, exportValue);
}
// Test alternative .pb for bootclasspath and systemclasspath.
TEST_F(DeriveClasspathTest, CustomInputLocation) {
AddJarToClasspath("/other", "/other/bcp-jar", BOOTCLASSPATH);
AddJarToClasspath("/other", "/other/systemserver-jar", SYSTEMSERVERCLASSPATH);
AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz",
SYSTEMSERVERCLASSPATH);
Args args = default_args_with_test_dir_;
args.system_bootclasspath_fragment = "/other/etc/classpaths/bootclasspath.pb";
args.system_systemserverclasspath_fragment = "/other/etc/classpaths/systemserverclasspath.pb";
ASSERT_TRUE(GenerateClasspathExports(args));
const std::vector<std::string> exportLines = ParseExportsFile();
std::vector<std::string> splitExportLine;
splitExportLine = SplitClasspathExportLine(exportLines[0]);
EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
const std::string expectedBcpJars(
"/apex/com.android.art/javalib/art"
":/other/bcp-jar"
":/apex/com.android.foo/javalib/foo");
EXPECT_EQ(expectedBcpJars, splitExportLine[2]);
splitExportLine = SplitClasspathExportLine(exportLines[2]);
EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
const std::string expectedSystemServerJars(
"/other/systemserver-jar"
":/apex/com.android.baz/javalib/baz");
EXPECT_EQ(expectedSystemServerJars, splitExportLine[2]);
}
// Test output location that can't be written to.
TEST_F(DeriveClasspathTest, NonWriteableOutputLocation) {
AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH);
AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
Args args = {
.output_path = "/system/non_writable_path",
.glob_pattern_prefix = working_dir(),
};
ASSERT_FALSE(GenerateClasspathExports(args));
}
TEST_F(DeriveClasspathTest, ScanOnlySpecificDirectories) {
AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/sys",
SYSTEMSERVERCLASSPATH);
AddJarToClasspath("/apex/com.android.bar", "/apex/com.android.bar/javalib/bar", BOOTCLASSPATH);
AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", BOOTCLASSPATH);
auto args_with_scan_dirs = default_args_with_test_dir_;
args_with_scan_dirs.scan_dirs.push_back("/apex/com.android.foo");
args_with_scan_dirs.scan_dirs.push_back("/apex/com.android.bar");
ASSERT_TRUE(GenerateClasspathExports(args_with_scan_dirs));
const std::vector<std::string> exportLines = ParseExportsFile();
std::vector<std::string> splitExportLine;
splitExportLine = SplitClasspathExportLine(exportLines[0]);
EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
// Not sorted. Maintains the ordering provided in scan_dirs
const std::string expectedJars(
"/apex/com.android.foo/javalib/foo"
":/apex/com.android.bar/javalib/bar");
EXPECT_EQ(expectedJars, splitExportLine[2]);
splitExportLine = SplitClasspathExportLine(exportLines[2]);
EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]);
EXPECT_EQ("/apex/com.android.foo/javalib/sys", splitExportLine[2]);
}
// Test apexes only export their own jars.
TEST_F(DeriveClasspathDeathTest, ApexJarsBelongToApex) {
// EXPECT_DEATH expects error messages in stderr, log there
android::base::SetLogger(android::base::StderrLogger);
AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
AddJarToClasspath("/apex/com.android.bar@12345.tmp", "/apex/com.android.bar/javalib/bar",
BOOTCLASSPATH);
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
AddJarToClasspath("/apex/com.android.baz@12345", "/apex/this/path/is/skipped", BOOTCLASSPATH);
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
AddJarToClasspath("/apex/com.android.bar", "/apex/wrong/path/bar", BOOTCLASSPATH);
EXPECT_DEATH(GenerateClasspathExports(default_args_with_test_dir_),
"must not export a jar.*wrong/path/bar");
}
// Test only bind mounted apexes are skipped
TEST_F(DeriveClasspathTest, OnlyBindMountedApexIsSkipped) {
AddJarToClasspath("/system", "/system/framework/jar", BOOTCLASSPATH);
// Normal APEX with format: /apex/<module-name>/*
AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH);
// Bind mounted APEX with format: /apex/<module-name>@<version>/*
AddJarToClasspath("/apex/com.android.bar@123", "/apex/com.android.bar/javalib/bar",
BOOTCLASSPATH);
// Temp mounted APEX with format: /apex/<module-name>@<version>.tmp/*
AddJarToClasspath("/apex/com.android.baz@123.tmp", "/apex/com.android.baz/javalib/baz",
BOOTCLASSPATH);
ASSERT_TRUE(GenerateClasspathExports(default_args_with_test_dir_));
const std::vector<std::string> exportLines = ParseExportsFile();
std::vector<std::string> splitExportLine;
splitExportLine = SplitClasspathExportLine(exportLines[0]);
EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]);
const std::string expectedJars(
"/system/framework/jar"
":/apex/com.android.baz/javalib/baz"
":/apex/com.android.foo/javalib/foo");
EXPECT_EQ(expectedJars, splitExportLine[2]);
}
// Test classpath fragments export jars for themselves.
TEST_F(DeriveClasspathDeathTest, WrongClasspathInFragments) {
// Valid configs
AddJarToClasspath("/system", "/system/framework/framework-jar", BOOTCLASSPATH);
AddJarToClasspath("/system", "/system/framework/service-jar", SYSTEMSERVERCLASSPATH);
// Manually create an invalid config with both BCP and SSCP jars...
ExportedClasspathsJars exported_jars;
Jar* jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/foo");
jar->set_classpath(BOOTCLASSPATH);
// note that DEX2OATBOOTCLASSPATH and BOOTCLASSPATH jars are expected to be in the same config
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/foo");
jar->set_classpath(DEX2OATBOOTCLASSPATH);
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/service-foo");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
// ...and write this config to bootclasspath.pb
WriteConfig(exported_jars, "/apex/com.android.foo/etc/classpaths/bootclasspath.pb");
EXPECT_DEATH(GenerateClasspathExports(default_args_with_test_dir_),
"must not export a jar for SYSTEMSERVERCLASSPATH");
}
TEST_F(DeriveClasspathDeathTest, CurrentSdkVersion) {
if (android_get_device_api_level() < __ANDROID_API_S__) {
GTEST_SKIP();
}
ExportedClasspathsJars exported_jars;
Jar* jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdkcurrent");
jar->set_min_sdk_version("current");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
WriteConfig(exported_jars, "/apex/com.android.foo/etc/classpaths/systemserverclasspath.pb");
EXPECT_DEATH(GenerateClasspathExports(default_args_with_test_dir_), "no conversion");
}
// Test jars with different sdk versions.
TEST_F(DeriveClasspathTest, SdkVersionsAreRespected) {
if (android_get_device_api_level() < __ANDROID_API_S__) {
GTEST_SKIP();
}
// List of jars expected to be in SYSTEMSERVERCLASSPATH
std::vector<std::string> expected_jars;
// Add an unbounded jar
AddJarToClasspath("/system", "/system/framework/unbounded", SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/system/framework/unbounded");
// Manually create a config with jars that set sdk versions...
ExportedClasspathsJars exported_jars;
// known released versions:
Jar* jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdk30");
jar->set_min_sdk_version(std::to_string(__ANDROID_API_R__));
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/minsdk30");
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/maxsdk30");
jar->set_max_sdk_version(std::to_string(__ANDROID_API_R__));
jar->set_classpath(SYSTEMSERVERCLASSPATH);
// Device's reported version:
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdklatest");
jar->set_min_sdk_version(std::to_string(android_get_device_api_level()));
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/minsdklatest");
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/maxsdklatest");
jar->set_max_sdk_version(std::to_string(android_get_device_api_level()));
jar->set_classpath(SYSTEMSERVERCLASSPATH);
if ("REL" == android::base::GetProperty("ro.build.version.codename", "")) {
expected_jars.push_back("/apex/com.android.foo/javalib/maxsdklatest");
}
// unknown SDK_INT+1 version
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdk_plus1");
jar->set_min_sdk_version(std::to_string(android_get_device_api_level() + 1));
jar->set_classpath(SYSTEMSERVERCLASSPATH);
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/maxsdk_plus1");
jar->set_max_sdk_version(std::to_string(android_get_device_api_level() + 1));
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/maxsdk_plus1");
// known min_sdk_version and future max_sdk_version
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdk30maxsdk10000");
jar->set_min_sdk_version(std::to_string(__ANDROID_API_R__));
jar->set_max_sdk_version(std::to_string(android_get_device_api_level() + 1));
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/minsdk30maxsdk10000");
// codename
if ("REL" != android::base::GetProperty("ro.build.version.codename", "")) {
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdkS");
jar->set_min_sdk_version("S");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/minsdkS");
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdkSv2");
jar->set_min_sdk_version("Sv2");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/minsdkSv2");
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/minsdkTiramisu");
jar->set_min_sdk_version("Tiramisu");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/minsdkTiramisu");
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/maxsdkS");
jar->set_max_sdk_version("S");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/maxsdkSv2");
jar->set_max_sdk_version("Sv2");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
jar = exported_jars.add_jars();
jar->set_path("/apex/com.android.foo/javalib/maxsdkZFutureSdkVersion");
jar->set_max_sdk_version("ZFutureSdkVersion");
jar->set_classpath(SYSTEMSERVERCLASSPATH);
expected_jars.push_back("/apex/com.android.foo/javalib/maxsdkZFutureSdkVersion");
}
// ...and write this config to systemserverclasspath.pb
WriteConfig(exported_jars, "/apex/com.android.foo/etc/classpaths/systemserverclasspath.pb");
// Generate and parse SYSTEMSERVERCLASSPATH
GenerateClasspathExports(default_args_with_test_dir_);
const std::vector<std::string> exportLines = ParseExportsFile();
const std::vector<std::string> splitExportLine = SplitClasspathExportLine(exportLines[2]);
const std::string exportValue = splitExportLine[2];
EXPECT_EQ(android::base::Join(expected_jars, ":"), exportValue);
}
} // namespace
} // namespace derive_classpath
} // namespace android
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
// Required for EXPECT_DEATH to work correctly
android::base::SetLogger(android::base::StderrLogger);
return RUN_ALL_TESTS();
}