blob: b91cb53712e9e12df305b06f705916ed2fb9aa16 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <gtest/gtest.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/function.h>
#include "./testing_object.h"
namespace {
using namespace tvm::ffi;
using namespace tvm::ffi::testing;
TEST(Map, Basic) {
Map<TInt, int> map0;
TInt k0(0);
map0.Set(k0, 1);
EXPECT_EQ(map0.size(), 1);
map0.Set(k0, 2);
EXPECT_EQ(map0.size(), 1);
auto it = map0.find(k0);
EXPECT_TRUE(it != map0.end());
EXPECT_EQ((*it).second, 2);
}
TEST(Map, PODKey) {
Map<Any, Any> map0;
// int as key
map0.Set(1, 2);
// float key is different
map0.Set(1.1, 3);
EXPECT_EQ(map0.size(), 2);
auto it = map0.find(1.1);
EXPECT_TRUE(it != map0.end());
EXPECT_EQ((*it).second.cast<int>(), 3);
}
TEST(Map, Object) {
TInt x(1);
TInt z(100);
TInt zz(1000);
Map<TInt, TInt> dict{{x, z}, {z, zz}};
EXPECT_EQ(dict.size(), 2);
EXPECT_TRUE(dict[x].same_as(z));
EXPECT_TRUE(dict.count(z));
EXPECT_TRUE(!dict.count(zz));
}
TEST(Map, Str) {
TInt x(1);
TInt z(100);
Map<String, TInt> dict{{"x", z}, {"z", z}};
EXPECT_EQ(dict.size(), 2);
EXPECT_TRUE(dict["x"].same_as(z));
}
TEST(Map, Mutate) {
TInt x(1);
TInt z(100);
TInt zz(1000);
Map<TInt, TInt> dict{{x, z}, {z, zz}};
EXPECT_TRUE(dict[x].same_as(z));
dict.Set(x, zz);
auto dict2 = dict;
EXPECT_EQ(dict2.count(z), 1);
dict.Set(zz, x);
EXPECT_EQ(dict2.count(zz), 0);
EXPECT_EQ(dict.count(zz), 1);
auto it = dict.find(zz);
EXPECT_TRUE(it != dict.end() && (*it).second.same_as(x));
it = dict2.find(zz);
EXPECT_TRUE(it == dict2.end());
}
TEST(Map, Clear) {
TInt x(1);
TInt z(100);
Map<TInt, TInt> dict{{x, z}, {z, z}};
EXPECT_EQ(dict.size(), 2);
dict.clear();
EXPECT_EQ(dict.size(), 0);
}
TEST(Map, Insert) {
auto check = [](const Map<String, int64_t>& result,
std::unordered_map<std::string, int64_t> expected) {
EXPECT_EQ(result.size(), expected.size());
for (const auto& kv : result) {
EXPECT_TRUE(expected.count(kv.first));
EXPECT_EQ(expected[kv.first], kv.second);
expected.erase(kv.first);
}
};
Map<String, int64_t> result;
std::unordered_map<std::string, int64_t> expected;
char key = 'a';
int64_t val = 1;
for (int i = 0; i < 26; ++i, ++key, ++val) {
std::string s(1, key);
result.Set(s, val);
expected[s] = val;
check(result, expected);
}
}
TEST(Map, Erase) {
auto check = [](const Map<String, int64_t>& result,
std::unordered_map<std::string, int64_t> expected) {
EXPECT_EQ(result.size(), expected.size());
for (const auto& kv : result) {
EXPECT_TRUE(expected.count(kv.first));
EXPECT_EQ(expected[kv.first], kv.second);
expected.erase(kv.first);
}
};
Map<String, int64_t> map{{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}};
std::unordered_map<std::string, int64_t> stl;
std::transform(map.begin(), map.end(), std::inserter(stl, stl.begin()),
[](auto&& p) { return std::make_pair(p.first, p.second); });
for (char c = 'a'; c <= 'e'; ++c) {
Map<String, int64_t> result = map;
std::unordered_map<std::string, int64_t> expected(stl);
std::string key(1, c);
result.erase(key);
expected.erase(key);
check(result, expected);
}
}
TEST(Map, AnyImplicitConversion) {
Map<Any, Any> map0;
map0.Set(1, 2);
map0.Set(2, 3.1);
EXPECT_EQ(map0.size(), 2);
// check will trigger copy
AnyView view0 = map0;
auto map1 = view0.cast<Map<int, double>>();
EXPECT_TRUE(!map1.same_as(map0));
EXPECT_EQ(map1[1], 2);
EXPECT_EQ(map1[2], 3.1);
EXPECT_EQ(map1.use_count(), 1);
auto map2 = view0.cast<Map<int, Any>>();
EXPECT_TRUE(map2.same_as(map0));
EXPECT_EQ(map2.use_count(), 2);
auto map3 = view0.cast<Map<Any, double>>();
EXPECT_TRUE(!map3.same_as(map0));
EXPECT_EQ(map3.use_count(), 1);
Map<Any, Any> map4{{"yes", 1.1}, {"no", 2.2}};
Any any1 = map4;
auto map5 = any1.cast<Map<String, double>>();
EXPECT_TRUE(map5.same_as(map4));
EXPECT_EQ(map5.use_count(), 3);
auto map6 = any1.cast<Map<String, Any>>();
EXPECT_TRUE(map6.same_as(map4));
EXPECT_EQ(map6.use_count(), 4);
EXPECT_EQ(map6["yes"].cast<double>(), 1.1);
EXPECT_EQ(map6["no"].cast<double>(), 2.2);
auto map7 = any1.cast<Map<Any, Any>>();
EXPECT_TRUE(map7.same_as(map4));
EXPECT_EQ(map7.use_count(), 5);
auto map8 = any1.cast<Map<Any, TPrimExpr>>();
EXPECT_TRUE(!map8.same_as(map4));
EXPECT_EQ(map8.use_count(), 1);
EXPECT_EQ(map8["yes"]->value, 1.1);
EXPECT_EQ(map8["no"]->value, 2.2);
}
TEST(Map, AnyConvertCheck) {
Map<Any, Any> map = {{11, 1.1}};
EXPECT_EQ(map[11].cast<double>(), 1.1);
AnyView view0 = map;
auto arr1 = view0.cast<Map<int64_t, double>>();
EXPECT_EQ(arr1[11], 1.1);
Any any1 = map;
using WrongMap = Map<int64_t, int64_t>;
EXPECT_THROW(
{
try {
[[maybe_unused]] auto arr2 = any1.cast<WrongMap>();
} catch (const Error& error) {
EXPECT_EQ(error.kind(), "TypeError");
std::string what = error.what();
EXPECT_NE(
what.find(
"Cannot convert from type `Map[K, some value is float]` to `Map<int, int>`"),
std::string::npos);
throw;
}
},
::tvm::ffi::Error);
using WrongMap2 = Map<TNumber, double>;
EXPECT_THROW(
{
try {
[[maybe_unused]] auto arr2 = any1.cast<WrongMap2>();
} catch (const Error& error) {
EXPECT_EQ(error.kind(), "TypeError");
std::string what = error.what();
EXPECT_NE(what.find("Cannot convert from type `Map[some key is int, V]` to "
"`Map<test.Number, float>`"),
std::string::npos);
throw;
}
},
::tvm::ffi::Error);
}
TEST(Map, FunctionGetItem) {
Function f = Function::FromTyped([](const MapObj* n, const Any& k) -> Any { return n->at(k); },
"map_get_item");
Map<String, int64_t> map{{"x", 1}, {"y", 2}};
Any k("x");
Any v = f(map, k);
EXPECT_EQ(v.cast<int>(), 1);
}
TEST(Map, Upcast) {
Map<int, int> m0 = {{1, 2}, {3, 4}};
Map<Any, Any> m1 = m0;
EXPECT_EQ(m1[1].cast<int>(), 2);
EXPECT_EQ(m1[3].cast<int>(), 4);
static_assert(type_subsumes_v<Map<Any, Any>, Map<String, int>>);
Map<String, Array<int>> m2 = {{"x", {1}}, {"y", {2}}};
Map<String, Array<Any>> m3 = m2;
}
template <typename K, typename V>
void PrintMap(const Map<K, V>& m0) {
std::cout << "{";
for (auto it = m0.begin(); it != m0.end(); ++it) {
if (it != m0.begin()) {
std::cout << ", ";
}
std::cout << (*it).first << ": " << (*it).second;
}
std::cout << "}" << std::endl;
}
TEST(Map, MapInsertOrder) {
// test that map preserves the insertion order
auto get_reverse_order = [](size_t size) {
std::vector<int> reverse_order;
for (int i = static_cast<int>(size); i != 0; --i) {
reverse_order.push_back(i - 1);
}
return reverse_order;
};
// NOLINTNEXTLINE(performance-unnecessary-value-param)
auto check_map = [&](Map<String, int> m0, size_t size, const std::vector<int>& order) {
auto lhs = m0.begin();
auto rhs = order.begin();
while (lhs != m0.end()) {
TVM_FFI_ICHECK_EQ((*lhs).first, "hello" + std::to_string(*rhs));
TVM_FFI_ICHECK_EQ((*lhs).second, *rhs);
++lhs;
++rhs;
}
lhs = m0.end();
rhs = order.begin() + static_cast<std::ptrdiff_t>(size);
do {
--lhs;
--rhs;
TVM_FFI_ICHECK_EQ((*lhs).first, "hello" + std::to_string(*rhs));
TVM_FFI_ICHECK_EQ((*lhs).second, *rhs);
} while (lhs != m0.begin());
};
auto check_order = [&](std::vector<int> order) {
Map<String, int> m0;
for (size_t i = 0; i < order.size(); ++i) {
m0.Set("hello" + std::to_string(order[i]), order[i]);
check_map(m0, i + 1, order);
}
check_map(m0, order.size(), order);
// erase a few items
m0.erase("hello" + std::to_string(order[0]));
auto item0 = order[0];
order.erase(order.begin());
check_map(m0, order.size(), order);
// erase the middle part
if (order.size() > 1) {
m0.erase("hello" + std::to_string(order[1]));
order.erase(order.begin() + 1);
check_map(m0, order.size(), order);
}
// erase the end
m0.erase("hello" + std::to_string(order.back()));
auto item2 = order.back();
order.erase(order.end() - 1);
check_map(m0, order.size(), order);
EXPECT_NE(m0.size(), 0);
// put back some items
order.push_back(item2);
m0.Set("hello" + std::to_string(item2), item2);
check_map(m0, order.size(), order);
order.push_back(item0);
m0.Set("hello" + std::to_string(item0), item0);
check_map(m0, order.size(), order);
};
// test with 17 items: DenseMapObj
check_order(get_reverse_order(17));
// test with 4 items: SmallMapObj
check_order(get_reverse_order(4));
}
TEST(Map, EmptyIter) {
Map<String, int> m0;
EXPECT_EQ(m0.begin(), m0.end());
// create a big map and then erase to keep a dense map empty
for (int i = 0; i < 10; ++i) {
m0.Set("hello" + std::to_string(i), i);
}
for (int i = 0; i < 10; ++i) {
m0.erase("hello" + std::to_string(i));
}
EXPECT_EQ(m0.size(), 0);
// now m0 is dense map with all empty slots
EXPECT_EQ(m0.begin(), m0.end());
}
TEST(Map, DuplicatedKeysInit) {
std::vector<std::pair<String, int>> data = {{"a", 1}, {"a", 2}, {"a", 3}};
Map<String, int> map(data.begin(), data.end());
EXPECT_EQ(map.size(), 1);
EXPECT_EQ(map["a"], 3);
}
} // namespace