| /* |
| * 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 METADATA_TESTING |
| |
| #include <audio_utils/Metadata.h> |
| #include <gtest/gtest.h> |
| #include <stdio.h> |
| |
| #include <error.h> |
| #include <iostream> |
| |
| 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 __attribute__((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 __attribute__((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 __attribute__((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); |
| printf("%s\n", 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]); |
| } |
| }; |
| |
| TEST(metadata_tests, bytestring_examples) { |
| ByteString bs; |
| |
| copyToByteString((int32_t)123, bs); |
| printf("123 -> %s\n", toString(bs).c_str()); |
| const ByteString ref1{ 0x7b, 0x00, 0x00, 0x00 }; |
| ASSERT_EQ(ref1, bs); |
| |
| bs.clear(); |
| // for copyToByteString use std::string instead of char array. |
| copyToByteString(std::string("hi"), bs); |
| printf("\"hi\" -> %s\n", toString(bs).c_str()); |
| const ByteString ref2{ 0x02, 0x00, 0x00, 0x00, 0x68, 0x69 }; |
| ASSERT_EQ(ref2, bs); |
| |
| bs.clear(); |
| Data d; |
| d.emplace("hello", "world"); |
| d.emplace("value", (int32_t)1000); |
| copyToByteString(d, bs); |
| printf("{{\"hello\", \"world\"}, {\"value\", 1000}} -> %s\n", toString(bs).c_str()); |
| const ByteString ref3{ |
| 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, |
| 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x05, 0x00, 0x00, |
| 0x00, 0x09, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, |
| 0x00, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x05, 0x00, |
| 0x00, 0x00, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x01, |
| 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe8, |
| 0x03, 0x00, 0x00}; |
| ASSERT_EQ(ref3, bs); |
| }; |
| |
| // Test C API from C++ |
| TEST(metadata_tests, c) { |
| audio_metadata_t *metadata = audio_metadata_create(); |
| 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); |
| |
| audio_metadata_put(metadata, "i32", (int32_t)1); |
| audio_metadata_put(metadata, "i64", (int64_t)2); |
| audio_metadata_put(metadata, "float", (float)3.1f); |
| audio_metadata_put(metadata, "double", (double)4.11); |
| audio_metadata_t *data = audio_metadata_create(); |
| audio_metadata_put(data, "string", "hello"); |
| audio_metadata_put(metadata, "data", data); |
| #if 0 // candidate function not viable: no known conversion |
| { |
| static const struct complex { |
| float re; |
| float im; |
| } prime = { -5.0, -4.0 }; |
| audio_metadata_put(metadata, "complex", prime); |
| } |
| #endif |
| audio_metadata_destroy(data); |
| |
| int32_t i32Val; |
| int64_t i64Val; |
| float floatVal; |
| double doubleVal; |
| char *strVal = nullptr; |
| audio_metadata_t *dataVal = nullptr; |
| ASSERT_EQ(0, audio_metadata_get(metadata, "i32", &i32Val)); |
| ASSERT_EQ(1, i32Val); |
| ASSERT_EQ(0, audio_metadata_get(metadata, "i64", &i64Val)); |
| ASSERT_EQ(2, i64Val); |
| ASSERT_EQ(0, audio_metadata_get(metadata, "float", &floatVal)); |
| ASSERT_EQ(3.1f, floatVal); |
| ASSERT_EQ(0, audio_metadata_get(metadata, "double", &doubleVal)); |
| ASSERT_EQ(4.11, doubleVal); |
| ASSERT_EQ(0, audio_metadata_get(metadata, "data", &dataVal)); |
| ASSERT_NE(dataVal, nullptr); |
| ASSERT_EQ(0, audio_metadata_get(dataVal, "string", &strVal)); |
| ASSERT_EQ(0, strcmp("hello", strVal)); |
| free(strVal); |
| audio_metadata_destroy(dataVal); |
| dataVal = nullptr; |
| ASSERT_EQ(-ENOENT, audio_metadata_get(metadata, "non_exist_key", &i32Val)); |
| audio_metadata_t *nullMetadata = nullptr; |
| ASSERT_EQ(-EINVAL, audio_metadata_get(nullMetadata, "i32", &i32Val)); |
| char *nullKey = nullptr; |
| ASSERT_EQ(-EINVAL, audio_metadata_get(metadata, nullKey, &i32Val)); |
| int *nullI32Val = nullptr; |
| ASSERT_EQ(-EINVAL, audio_metadata_get(metadata, "i32", nullI32Val)); |
| |
| uint8_t *bs = nullptr; |
| ssize_t length = byte_string_from_audio_metadata(metadata, &bs); |
| ASSERT_GT(length, 0); // if gt 0, the bs has been updated to a new value. |
| ASSERT_EQ((size_t)length, audio_metadata_byte_string_len(bs)); |
| ASSERT_EQ((size_t)length, dataByteStringLen(bs)); |
| ASSERT_EQ(byteStringFromData(d).size(), ByteString(bs, bs + length).size()); |
| audio_metadata_t *metadataFromBs = audio_metadata_from_byte_string(bs, length); |
| free(bs); |
| bs = nullptr; |
| length = byte_string_from_audio_metadata(metadataFromBs, &bs); |
| ASSERT_GT(length, 0); // if gt 0, the bs has been updated to a new value. |
| ASSERT_EQ(byteStringFromData(d), ByteString(bs, bs + length)); |
| ASSERT_EQ((size_t)length, audio_metadata_byte_string_len(bs)); |
| ASSERT_EQ((size_t)length, dataByteStringLen(bs)); |
| free(bs); |
| bs = nullptr; |
| audio_metadata_destroy(metadataFromBs); |
| ASSERT_EQ(-EINVAL, byte_string_from_audio_metadata(nullMetadata, &bs)); |
| uint8_t **nullBs = nullptr; |
| ASSERT_EQ(-EINVAL, byte_string_from_audio_metadata(metadata, nullBs)); |
| |
| ASSERT_EQ(1, audio_metadata_erase(metadata, "data")); |
| // initialize to a known invalid pointer |
| dataVal = reinterpret_cast<audio_metadata_t *>(reinterpret_cast<intptr_t>(nullptr) + 1); |
| ASSERT_EQ(-ENOENT, audio_metadata_get(metadata, "data", &dataVal)); |
| // confirm that a failed get will assign nullptr; be sure to |
| // update test if API behavior is changed to not assign nullptr on error |
| ASSERT_EQ(nullptr, dataVal); |
| ASSERT_EQ(0, audio_metadata_erase(metadata, "data")); |
| ASSERT_EQ(-EINVAL, audio_metadata_erase(nullMetadata, "key")); |
| ASSERT_EQ(-EINVAL, audio_metadata_erase(metadata, nullKey)); |
| |
| audio_metadata_destroy(metadata); |
| }; |
| |
| TEST(metadata_tests, empty_data_c) { |
| std::unique_ptr<audio_metadata_t, decltype(&audio_metadata_destroy)> |
| metadata{audio_metadata_create(), audio_metadata_destroy}; // empty metadata container. |
| uint8_t *bs = nullptr; |
| ssize_t length = byte_string_from_audio_metadata(metadata.get(), &bs); |
| ASSERT_GT(length, 0); // if gt 0, the bs has been updated to a new value. |
| std::unique_ptr<uint8_t, decltype(&free)> bs_scoped_deleter{bs, free}; |
| ASSERT_EQ((size_t)length, audio_metadata_byte_string_len(bs)); |
| ASSERT_EQ((size_t)length, dataByteStringLen(bs)); |
| |
| Data d; // empty metadata container. |
| ASSERT_EQ(byteStringFromData(d).size(), ByteString(bs, bs + length).size()); |
| std::unique_ptr<audio_metadata_t, decltype(&audio_metadata_destroy)> |
| metadataFromBs{audio_metadata_from_byte_string(bs, length), audio_metadata_destroy}; |
| length = byte_string_from_audio_metadata(metadataFromBs.get(), &bs); |
| ASSERT_GT(length, 0); // if gt 0, the bs has been updated to a new value. |
| bs_scoped_deleter.reset(bs); |
| ASSERT_EQ(byteStringFromData(d), ByteString(bs, bs + length)); |
| ASSERT_EQ((size_t)length, audio_metadata_byte_string_len(bs)); |
| ASSERT_EQ((size_t)length, dataByteStringLen(bs)); |
| }; |