/* * Copyright (C) 2018 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 <unistd.h> #include <string> #include <android-base/file.h> #include <android-base/scopeguard.h> #include <android-base/test_utils.h> #include <gtest/gtest.h> #include "CppGen.h" namespace { constexpr const char* kTestSyspropFile = R"(owner: Platform module: "android.sysprop.PlatformProperties" prop { api_name: "test_double" type: Double prop_name: "android.test_double" scope: Internal access: ReadWrite } prop { api_name: "test_int" type: Integer prop_name: "android.test_int" scope: Public access: ReadWrite } prop { api_name: "test.string" type: String prop_name: "android.test.string" scope: System access: ReadWrite } prop { api_name: "test.enum" type: Enum prop_name: "android.test.enum" enum_values: "a|b|c|D|e|f|G" scope: Internal access: ReadWrite } prop { api_name: "test_BOOLeaN" type: Boolean prop_name: "ro.android.test.b" scope: Public access: Writeonce } prop { api_name: "android.os_test-long" type: Long scope: System access: ReadWrite } prop { api_name: "test_double_list" type: DoubleList scope: Internal access: ReadWrite } prop { api_name: "test_list_int" type: IntegerList scope: Public access: ReadWrite } prop { api_name: "test.strlist" type: StringList scope: System access: ReadWrite } prop { api_name: "el" type: EnumList enum_values: "enu|mva|lue" scope: Internal access: ReadWrite } )"; constexpr const char* kExpectedHeaderOutput = R"(// Generated by the sysprop generator. DO NOT EDIT! #pragma once #include <cstdint> #include <optional> #include <string> #include <vector> namespace android::sysprop::PlatformProperties { std::optional<double> test_double(); bool test_double(const std::optional<double>& value); std::optional<std::int32_t> test_int(); bool test_int(const std::optional<std::int32_t>& value); std::optional<std::string> test_string(); bool test_string(const std::optional<std::string>& value); enum class test_enum_values { A, B, C, D, E, F, G, }; std::optional<test_enum_values> test_enum(); bool test_enum(const std::optional<test_enum_values>& value); std::optional<bool> test_BOOLeaN(); bool test_BOOLeaN(const std::optional<bool>& value); std::optional<std::int64_t> android_os_test_long(); bool android_os_test_long(const std::optional<std::int64_t>& value); std::vector<std::optional<double>> test_double_list(); bool test_double_list(const std::vector<std::optional<double>>& value); std::vector<std::optional<std::int32_t>> test_list_int(); bool test_list_int(const std::vector<std::optional<std::int32_t>>& value); std::vector<std::optional<std::string>> test_strlist(); bool test_strlist(const std::vector<std::optional<std::string>>& value); enum class el_values { ENU, MVA, LUE, }; std::vector<std::optional<el_values>> el(); bool el(const std::vector<std::optional<el_values>>& value); } // namespace android::sysprop::PlatformProperties )"; constexpr const char* kExpectedSystemHeaderOutput = R"(// Generated by the sysprop generator. DO NOT EDIT! #pragma once #include <cstdint> #include <optional> #include <string> #include <vector> namespace android::sysprop::PlatformProperties { std::optional<std::int32_t> test_int(); std::optional<std::string> test_string(); std::optional<bool> test_BOOLeaN(); std::optional<std::int64_t> android_os_test_long(); std::vector<std::optional<std::int32_t>> test_list_int(); std::vector<std::optional<std::string>> test_strlist(); } // namespace android::sysprop::PlatformProperties )"; constexpr const char* kExpectedSourceOutput = R"(// Generated by the sysprop generator. DO NOT EDIT! #include <properties/PlatformProperties.sysprop.h> #include <cctype> #include <cerrno> #include <cstdio> #include <cstring> #include <limits> #include <utility> #include <strings.h> #include <sys/system_properties.h> #include <android-base/parseint.h> #include <log/log.h> namespace { using namespace android::sysprop::PlatformProperties; template <typename T> T DoParse(const char* str); constexpr const std::pair<const char*, test_enum_values> test_enum_list[] = { {"a", test_enum_values::A}, {"b", test_enum_values::B}, {"c", test_enum_values::C}, {"D", test_enum_values::D}, {"e", test_enum_values::E}, {"f", test_enum_values::F}, {"G", test_enum_values::G}, }; template <> std::optional<test_enum_values> DoParse(const char* str) { for (auto [name, val] : test_enum_list) { if (strcmp(str, name) == 0) { return val; } } return std::nullopt; } std::string FormatValue(std::optional<test_enum_values> value) { if (!value) return ""; for (auto [name, val] : test_enum_list) { if (val == *value) { return name; } } LOG_ALWAYS_FATAL("Invalid value %d for property android.test.enum", static_cast<std::int32_t>(*value)); __builtin_unreachable(); } constexpr const std::pair<const char*, el_values> el_list[] = { {"enu", el_values::ENU}, {"mva", el_values::MVA}, {"lue", el_values::LUE}, }; template <> std::optional<el_values> DoParse(const char* str) { for (auto [name, val] : el_list) { if (strcmp(str, name) == 0) { return val; } } return std::nullopt; } std::string FormatValue(std::optional<el_values> value) { if (!value) return ""; for (auto [name, val] : el_list) { if (val == *value) { return name; } } LOG_ALWAYS_FATAL("Invalid value %d for property el", static_cast<std::int32_t>(*value)); __builtin_unreachable(); } template <typename T> constexpr bool is_vector = false; template <typename T> constexpr bool is_vector<std::vector<T>> = true; template <> [[maybe_unused]] std::optional<bool> DoParse(const char* str) { static constexpr const char* kYes[] = {"1", "true"}; static constexpr const char* kNo[] = {"0", "false"}; for (const char* yes : kYes) { if (strcasecmp(yes, str) == 0) return std::make_optional(true); } for (const char* no : kNo) { if (strcasecmp(no, str) == 0) return std::make_optional(false); } return std::nullopt; } template <> [[maybe_unused]] std::optional<std::int32_t> DoParse(const char* str) { std::int32_t ret; return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt; } template <> [[maybe_unused]] std::optional<std::int64_t> DoParse(const char* str) { std::int64_t ret; return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt; } template <> [[maybe_unused]] std::optional<double> DoParse(const char* str) { int old_errno = errno; errno = 0; char* end; double ret = std::strtod(str, &end); if (errno != 0) { return std::nullopt; } if (str == end || *end != '\0') { errno = EINVAL; return std::nullopt; } errno = old_errno; return std::make_optional(ret); } template <> [[maybe_unused]] std::optional<std::string> DoParse(const char* str) { return *str == '\0' ? std::nullopt : std::make_optional(str); } template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) { Vec ret; const char* p = str; for (;;) { const char* found = p; while (*found != '\0' && *found != ',') { ++found; } std::string value(p, found); ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str())); if (*found == '\0') break; p = found + 1; } return ret; } template <typename T> inline T TryParse(const char* str) { if constexpr(is_vector<T>) { return DoParseList<T>(str); } else { return DoParse<T>(str); } } [[maybe_unused]] std::string FormatValue(const std::optional<std::int32_t>& value) { return value ? std::to_string(*value) : ""; } [[maybe_unused]] std::string FormatValue(const std::optional<std::int64_t>& value) { return value ? std::to_string(*value) : ""; } [[maybe_unused]] std::string FormatValue(const std::optional<double>& value) { if (!value) return ""; char buf[1024]; std::sprintf(buf, "%.*g", std::numeric_limits<double>::max_digits10, *value); return buf; } [[maybe_unused]] std::string FormatValue(const std::optional<bool>& value) { return value ? (*value ? "true" : "false") : ""; } template <typename T> [[maybe_unused]] std::string FormatValue(const std::vector<T>& value) { if (value.empty()) return ""; std::string ret; bool first = true; for (auto&& element : value) { if (!first) ret += ","; else first = false; if constexpr(std::is_same_v<T, std::optional<std::string>>) { if (element) ret += *element; } else { ret += FormatValue(element); } } return ret; } template <typename T> T GetProp(const char* key) { T ret; auto pi = __system_property_find(key); if (pi != nullptr) { __system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) { *static_cast<T*>(cookie) = TryParse<T>(value); }, &ret); } return ret; } } // namespace namespace android::sysprop::PlatformProperties { std::optional<double> test_double() { return GetProp<std::optional<double>>("android.test_double"); } bool test_double(const std::optional<double>& value) { return __system_property_set("android.test_double", FormatValue(value).c_str()) == 0; } std::optional<std::int32_t> test_int() { return GetProp<std::optional<std::int32_t>>("android.test_int"); } bool test_int(const std::optional<std::int32_t>& value) { return __system_property_set("android.test_int", FormatValue(value).c_str()) == 0; } std::optional<std::string> test_string() { return GetProp<std::optional<std::string>>("android.test.string"); } bool test_string(const std::optional<std::string>& value) { return __system_property_set("android.test.string", value ? value->c_str() : "") == 0; } std::optional<test_enum_values> test_enum() { return GetProp<std::optional<test_enum_values>>("android.test.enum"); } bool test_enum(const std::optional<test_enum_values>& value) { return __system_property_set("android.test.enum", FormatValue(value).c_str()) == 0; } std::optional<bool> test_BOOLeaN() { return GetProp<std::optional<bool>>("ro.android.test.b"); } bool test_BOOLeaN(const std::optional<bool>& value) { return __system_property_set("ro.android.test.b", FormatValue(value).c_str()) == 0; } std::optional<std::int64_t> android_os_test_long() { return GetProp<std::optional<std::int64_t>>("android.os_test-long"); } bool android_os_test_long(const std::optional<std::int64_t>& value) { return __system_property_set("android.os_test-long", FormatValue(value).c_str()) == 0; } std::vector<std::optional<double>> test_double_list() { return GetProp<std::vector<std::optional<double>>>("test_double_list"); } bool test_double_list(const std::vector<std::optional<double>>& value) { return __system_property_set("test_double_list", FormatValue(value).c_str()) == 0; } std::vector<std::optional<std::int32_t>> test_list_int() { return GetProp<std::vector<std::optional<std::int32_t>>>("test_list_int"); } bool test_list_int(const std::vector<std::optional<std::int32_t>>& value) { return __system_property_set("test_list_int", FormatValue(value).c_str()) == 0; } std::vector<std::optional<std::string>> test_strlist() { return GetProp<std::vector<std::optional<std::string>>>("test.strlist"); } bool test_strlist(const std::vector<std::optional<std::string>>& value) { return __system_property_set("test.strlist", FormatValue(value).c_str()) == 0; } std::vector<std::optional<el_values>> el() { return GetProp<std::vector<std::optional<el_values>>>("el"); } bool el(const std::vector<std::optional<el_values>>& value) { return __system_property_set("el", FormatValue(value).c_str()) == 0; } } // namespace android::sysprop::PlatformProperties )"; } // namespace using namespace std::string_literals; TEST(SyspropTest, CppGenTest) { TemporaryDir temp_dir; std::string temp_sysprop_path = temp_dir.path + "/PlatformProperties.sysprop"s; ASSERT_TRUE( android::base::WriteStringToFile(kTestSyspropFile, temp_sysprop_path)); auto sysprop_deleter = android::base::make_scope_guard( [&] { unlink(temp_sysprop_path.c_str()); }); std::string err; ASSERT_TRUE(GenerateCppFiles( temp_sysprop_path, temp_dir.path, temp_dir.path + "/system"s, temp_dir.path, "properties/PlatformProperties.sysprop.h", &err)); ASSERT_TRUE(err.empty()); std::string header_output_path = temp_dir.path + "/PlatformProperties.sysprop.h"s; std::string system_header_output_path = temp_dir.path + "/system/PlatformProperties.sysprop.h"s; std::string source_output_path = temp_dir.path + "/PlatformProperties.sysprop.cpp"s; auto generated_file_deleter = android::base::make_scope_guard([&] { unlink(header_output_path.c_str()); unlink(system_header_output_path.c_str()); unlink(source_output_path.c_str()); }); std::string header_output; ASSERT_TRUE(android::base::ReadFileToString(header_output_path, &header_output, true)); EXPECT_EQ(header_output, kExpectedHeaderOutput); std::string system_header_output; ASSERT_TRUE(android::base::ReadFileToString(system_header_output_path, &system_header_output, true)); EXPECT_EQ(system_header_output, kExpectedSystemHeaderOutput); std::string source_output; ASSERT_TRUE(android::base::ReadFileToString(source_output_path, &source_output, true)); EXPECT_EQ(source_output, kExpectedSourceOutput); }