| /* |
| * Copyright 2020 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. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "audio_utils_metadata_tests" |
| |
| #define METADATA_TESTING |
| |
| #include <audio_utils/Metadata.h> |
| #include <gtest/gtest.h> |
| #include <iostream> |
| #include <log/log.h> |
| |
| using namespace android::audio_utils::metadata; |
| |
| // Preferred: Key in header - a constexpr which is created by the compiler. |
| inline constexpr CKey<std::string> ITS_NAME_IS("its_name_is"); |
| |
| // Not preferred: Key which is created at run-time. |
| inline const Key<std::string> MY_NAME_IS("my_name_is"); |
| |
| // The Metadata table |
| inline constexpr CKey<Data> TABLE("table"); |
| |
| #ifdef METADATA_TESTING |
| |
| // Validate recursive typing on "Datum". |
| inline constexpr CKey<std::vector<Datum>> VECTOR("vector"); |
| inline constexpr CKey<std::pair<Datum, Datum>> PAIR("pair"); |
| |
| // Validate that we move instead of copy. |
| inline constexpr CKey<MoveCount> MOVE_COUNT("MoveCount"); |
| |
| // Validate recursive container support. |
| inline constexpr CKey<std::vector<std::vector<std::pair<std::string, short>>>> FUNKY("funky"); |
| |
| // Validate structured binding parceling. |
| inline constexpr CKey<Arbitrary> ARBITRARY("arbitrary"); |
| #endif |
| |
| std::string toString(const ByteString &bs) { |
| std::stringstream ss; |
| ss << "{\n" << std::hex; |
| if (bs.size() > 0) { |
| for (size_t i = 0; ; ++i) { |
| if ((i & 7) == 0) { |
| ss << " "; |
| } |
| ss << "0x" << std::setfill('0') << std::setw(2) << (unsigned)bs[i]; |
| if (i == bs.size() - 1) { |
| break; |
| } else if ((i & 7) == 7) { |
| ss << ",\n"; |
| } else { |
| ss << ", "; |
| } |
| } |
| } |
| ss << "\n}\n"; |
| return ss.str(); |
| } |
| |
| TEST(metadata_tests, basic_datum) { |
| Datum d; |
| d = "abc"; |
| //ASSERT_EQ("abc", std::any_cast<const char *>(d)); |
| ASSERT_EQ("abc", std::any_cast<std::string>(d)); |
| //d = std::vector<int>(); |
| |
| Datum lore((int32_t) 10); |
| d = lore; |
| ASSERT_EQ(10, std::any_cast<int32_t>(d)); |
| |
| // TODO: should we enable Datum to copy from std::any if the types |
| // are correct? The problem is how to signal failure. |
| std::any arg = (int)1; |
| // Datum invalid = arg; // this doesn't work. |
| |
| struct dummy { |
| int value = 0; |
| }; |
| |
| // check apply with a void function |
| { |
| // try to apply with an invalid argument |
| int value = 0; |
| |
| arg = dummy{}; // not an expected type, apply will fail with false. |
| std::any result; |
| |
| ASSERT_FALSE(primitive_metadata_types::apply([&](auto *t __unused) { |
| value++; |
| }, &arg, &result)); |
| |
| ASSERT_EQ(0, value); // never invoked. |
| ASSERT_FALSE(result.has_value()); // no value returned. |
| |
| // try to apply with a valid argument. |
| arg = (int)1; |
| |
| ASSERT_TRUE(primitive_metadata_types::apply([&](auto *t __unused) { |
| value++; |
| }, &arg, &result)); |
| |
| ASSERT_EQ(1, value); // invoked once. |
| ASSERT_FALSE(result.has_value()); // no value returned (function returns void). |
| } |
| |
| // check apply with a function that returns 2. |
| { |
| int value = 0; |
| arg = (int)1; |
| std::any result; |
| |
| ASSERT_TRUE(primitive_metadata_types::apply([&](auto *t __unused) { |
| value++; |
| return (int32_t)2; |
| }, &arg, &result)); |
| |
| ASSERT_EQ(1, value); // invoked once. |
| ASSERT_EQ(2, std::any_cast<int32_t>(result)); // 2 returned |
| } |
| |
| #ifdef METADATA_TESTING |
| // Checks the number of moves versus copies as the datum flows through Data. |
| // the counters should increment each time a MoveCount gets copied or |
| // moved. |
| |
| // Datum mc = MoveCount(); |
| |
| Datum mc{MoveCount()}; |
| ASSERT_TRUE(1 >= std::any_cast<MoveCount>(mc).mMoveCount); // no more than 1 move. |
| ASSERT_EQ(0, std::any_cast<MoveCount>(&mc)->mCopyCount); // no copies |
| ASSERT_EQ(1, std::any_cast<MoveCount>(mc).mCopyCount); // Note: any_cast on value copies. |
| |
| |
| // serialize |
| ByteString bs; |
| ASSERT_TRUE(copyToByteString(mc, bs)); |
| // deserialize |
| size_t idx = 0; |
| Datum parceled; |
| ASSERT_TRUE(copyFromByteString(&parceled, bs, idx, nullptr /* unknowns */)); |
| |
| // everything OK with the received data? |
| ASSERT_EQ(bs.size(), idx); // no data left over. |
| ASSERT_TRUE(parceled.has_value()); // we have a value. |
| |
| // confirm no copies. |
| ASSERT_TRUE(2 >= std::any_cast<MoveCount>(&parceled)->mMoveCount); // no more than 2 moves. |
| ASSERT_EQ(0, std::any_cast<MoveCount>(&parceled)->mCopyCount); |
| #endif |
| } |
| |
| TEST(metadata_tests, basic_data) { |
| Data d; |
| d.emplace("int32", (int32_t)1); |
| d.emplace("int64", (int64_t)2); |
| d.emplace("float", (float)3.1f); |
| d.emplace("double", (double)4.11); |
| d.emplace("string", "hello"); |
| d["string2"] = "world"; |
| |
| // Put with typed keys |
| d.put(MY_NAME_IS, "neo"); |
| d[ITS_NAME_IS] = "spot"; |
| |
| ASSERT_EQ(1, std::any_cast<int32_t>(d["int32"])); |
| ASSERT_EQ(2, std::any_cast<int64_t>(d["int64"])); |
| ASSERT_EQ(3.1f, std::any_cast<float>(d["float"])); |
| ASSERT_EQ(4.11, std::any_cast<double>(d["double"])); |
| ASSERT_EQ("hello", std::any_cast<std::string>(d["string"])); |
| ASSERT_EQ("world", std::any_cast<std::string>(d["string2"])); |
| |
| // Get with typed keys |
| ASSERT_EQ("neo", *d.get_ptr(MY_NAME_IS)); |
| ASSERT_EQ("spot", *d.get_ptr(ITS_NAME_IS)); |
| |
| ASSERT_EQ("neo", d[MY_NAME_IS]); |
| ASSERT_EQ("spot", d[ITS_NAME_IS]); |
| |
| ByteString bs = byteStringFromData(d); |
| Data data = dataFromByteString(bs); |
| ASSERT_EQ((size_t)8, data.size()); |
| |
| ASSERT_EQ(1, std::any_cast<int32_t>(data["int32"])); |
| ASSERT_EQ(2, std::any_cast<int64_t>(data["int64"])); |
| ASSERT_EQ(3.1f, std::any_cast<float>(data["float"])); |
| ASSERT_EQ(4.11, std::any_cast<double>(data["double"])); |
| ASSERT_EQ("hello", std::any_cast<std::string>(data["string"])); |
| ASSERT_EQ("neo", *data.get_ptr(MY_NAME_IS)); |
| ASSERT_EQ("spot", *data.get_ptr(ITS_NAME_IS)); |
| |
| data[MY_NAME_IS] = "one"; |
| ASSERT_EQ("one", data[MY_NAME_IS]); |
| |
| // Keys are typed, so this fails to compile. |
| // data->put(MY_NAME_IS, 10); |
| |
| #ifdef METADATA_TESTING |
| // Checks the number of moves versus copies as the Datum goes to |
| // Data and then parceled and unparceled. |
| // The counters should increment each time a MoveCount gets copied or |
| // moved. |
| { |
| Data d2; |
| d2[MOVE_COUNT] = MoveCount(); // should be moved. |
| |
| ASSERT_TRUE(1 >= d2[MOVE_COUNT].mMoveCount); // no more than one move. |
| ASSERT_EQ(0, d2[MOVE_COUNT].mCopyCount); // no copies |
| |
| ByteString bs = byteStringFromData(d2); |
| Data d3 = dataFromByteString(bs); |
| |
| ASSERT_EQ(0, d3[MOVE_COUNT].mCopyCount); // no copies |
| ASSERT_TRUE(2 >= d3[MOVE_COUNT].mMoveCount); // no more than 2 moves after parceling |
| } |
| #endif |
| } |
| |
| TEST(metadata_tests, complex_data) { |
| Data small; |
| Data big; |
| |
| small[MY_NAME_IS] = "abc"; |
| #ifdef METADATA_TESTING |
| small[MOVE_COUNT] = MoveCount{}; |
| #endif |
| big[TABLE] = small; // ONE COPY HERE of the MoveCount (embedded in small). |
| |
| #ifdef METADATA_TESTING |
| big[VECTOR] = std::vector<Datum>{small, small}; |
| big[PAIR] = std::make_pair<Datum, Datum>(small, small); |
| ASSERT_EQ(1, big[TABLE][MOVE_COUNT].mCopyCount); // one copy done for small. |
| |
| big[FUNKY] = std::vector<std::vector<std::pair<std::string, short>>>{ |
| {{"a", 1}, {"b", 2}}, |
| {{"c", 3}, {"d", 4}}, |
| }; |
| |
| // struct Arbitrary { int i0; std::vector<int> v1; std::pair<int, int> p2; }; |
| big[ARBITRARY] = Arbitrary{0, {1, 2, 3}, {4, 5}}; |
| #endif |
| |
| // Try round-trip conversion to a ByteString. |
| ByteString bs = byteStringFromData(big); |
| Data data = dataFromByteString(bs); |
| #ifdef METADATA_TESTING |
| ASSERT_EQ((size_t)5, data.size()); |
| #else |
| ASSERT_EQ((size_t)1, data.size()); |
| #endif |
| |
| // Nested tables make sense. |
| ASSERT_EQ("abc", data[TABLE][MY_NAME_IS]); |
| |
| #ifdef METADATA_TESTING |
| // TODO: Maybe we don't need the vector or the pair. |
| ASSERT_EQ("abc", std::any_cast<Data>(data[VECTOR][1])[MY_NAME_IS]); |
| ASSERT_EQ("abc", std::any_cast<Data>(data[PAIR].first)[MY_NAME_IS]); |
| ASSERT_EQ(1, data[TABLE][MOVE_COUNT].mCopyCount); // no additional copies. |
| |
| auto funky = data[FUNKY]; |
| ASSERT_EQ("a", funky[0][0].first); |
| ASSERT_EQ(4, funky[1][1].second); |
| |
| auto arbitrary = data[ARBITRARY]; |
| ASSERT_EQ(0, arbitrary.i0); |
| ASSERT_EQ(2, arbitrary.v1[1]); |
| ASSERT_EQ(4, arbitrary.p2.first); |
| #endif |
| } |
| |
| // DO NOT CHANGE THIS after R, but add a new test. |
| TEST(metadata_tests, compatibility_R) { |
| Data d; |
| d.emplace("i32", (int32_t)1); |
| d.emplace("i64", (int64_t)2); |
| d.emplace("float", (float)3.1f); |
| d.emplace("double", (double)4.11); |
| Data s; |
| s.emplace("string", "hello"); |
| d.emplace("data", s); |
| |
| ByteString bs = byteStringFromData(d); |
| ALOGD("%s", toString(bs).c_str()); |
| |
| // Since we use a map instead of a hashmap |
| // layout order of elements is precisely defined. |
| ByteString reference = { |
| 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, |
| 0x64, 0x61, 0x74, 0x61, 0x06, 0x00, 0x00, 0x00, |
| 0x1f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, |
| 0x06, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, |
| 0x6e, 0x67, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, |
| 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x68, 0x65, |
| 0x6c, 0x6c, 0x6f, 0x06, 0x00, 0x00, 0x00, 0x64, |
| 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x04, 0x00, 0x00, |
| 0x00, 0x08, 0x00, 0x00, 0x00, 0x71, 0x3d, 0x0a, |
| 0xd7, 0xa3, 0x70, 0x10, 0x40, 0x05, 0x00, 0x00, |
| 0x00, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x03, 0x00, |
| 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x66, 0x66, |
| 0x46, 0x40, 0x03, 0x00, 0x00, 0x00, 0x69, 0x33, |
| 0x32, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, |
| 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, |
| 0x00, 0x69, 0x36, 0x34, 0x02, 0x00, 0x00, 0x00, |
| 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00 |
| }; |
| ASSERT_EQ(reference, bs); |
| |
| Data decoded = dataFromByteString(bs); |
| |
| // TODO: data equality. |
| // ASSERT_EQ(decoded, d); |
| |
| ASSERT_EQ(1, std::any_cast<int32_t>(decoded["i32"])); |
| ASSERT_EQ(2, std::any_cast<int64_t>(decoded["i64"])); |
| ASSERT_EQ(3.1f, std::any_cast<float>(decoded["float"])); |
| ASSERT_EQ(4.11, std::any_cast<double>(decoded["double"])); |
| Data decoded_s = std::any_cast<Data>(decoded["data"]); |
| |
| ASSERT_EQ("hello", std::any_cast<std::string>(s["string"])); |
| |
| { |
| ByteString unknownData = reference; |
| unknownData[12] = 0xff; |
| Data decoded2 = dataFromByteString(unknownData); |
| ASSERT_EQ((size_t)0, decoded2.size()); |
| |
| ByteStringUnknowns unknowns; |
| Data decoded3 = dataFromByteString(unknownData, &unknowns); |
| ASSERT_EQ((size_t)4, decoded3.size()); |
| ASSERT_EQ((size_t)1, unknowns.size()); |
| ASSERT_EQ((unsigned)0xff, unknowns[0]); |
| } |
| |
| { |
| ByteString unknownDouble = reference; |
| ASSERT_EQ(0x4, unknownDouble[0x3d]); |
| unknownDouble[0x3d] = 0xfe; |
| Data decoded2 = dataFromByteString(unknownDouble); |
| ASSERT_EQ((size_t)0, decoded2.size()); |
| |
| ByteStringUnknowns unknowns; |
| Data decoded3 = dataFromByteString(unknownDouble, &unknowns); |
| ASSERT_EQ((size_t)4, decoded3.size()); |
| ASSERT_EQ((size_t)1, unknowns.size()); |
| ASSERT_EQ((unsigned)0xfe, unknowns[0]); |
| } |
| }; |