| /* |
| * 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/c_api.h> |
| #include <tvm/ffi/container/array.h> |
| #include <tvm/ffi/container/map.h> |
| #include <tvm/ffi/container/variant.h> |
| #include <tvm/ffi/dtype.h> |
| #include <tvm/ffi/extra/json.h> |
| #include <tvm/ffi/function.h> |
| #include <tvm/ffi/object.h> |
| #include <tvm/ffi/optional.h> |
| #include <tvm/ffi/reflection/accessor.h> |
| #include <tvm/ffi/string.h> |
| |
| // Forward declarations for exported FFI functions |
| extern "C" { |
| // NOLINTNEXTLINE(bugprone-reserved-identifier) |
| int __tvm_ffi__metadata_testing_dll_schema_id_int(void*, const TVMFFIAny*, int32_t, TVMFFIAny*); |
| // NOLINTNEXTLINE(bugprone-reserved-identifier) |
| int __tvm_ffi__metadata_testing_dll_test_add_with_docstring(void*, const TVMFFIAny*, int32_t, |
| TVMFFIAny*); |
| // NOLINTNEXTLINE(bugprone-reserved-identifier) |
| int __tvm_ffi__doc_testing_dll_test_add_with_docstring(void*, const TVMFFIAny*, int32_t, |
| TVMFFIAny*); |
| } |
| |
| namespace { |
| |
| using namespace tvm::ffi; |
| using namespace tvm::ffi::reflection; |
| |
| // Helper to call metadata FFI functions and return the String result |
| static String CallMetadataFunc(int (*func)(void*, const TVMFFIAny*, int32_t, TVMFFIAny*)) { |
| return Function::InvokeExternC(nullptr, func).cast<String>(); |
| } |
| |
| static std::string ParseMetadataToSchema(const String& metadata) { |
| return json::Parse(metadata) |
| .cast<Map<String, Any>>()["type_schema"] // |
| .cast<String>(); // |
| } |
| |
| static std::string ParseMetadataToSchema(const TVMFFIByteArray& metadata) { |
| return json::Parse(String(metadata)) |
| .cast<Map<String, Any>>()["type_schema"] // |
| .cast<String>(); // |
| } |
| |
| TEST(Schema, GlobalFuncTypeSchema) { |
| // Helper to fetch global function type schema via the exposed utility |
| Function get_metadata = Function::GetGlobalRequired("ffi.GetGlobalFuncMetadata"); |
| auto fetch = [&](const char* name) -> std::string { |
| String metadata = get_metadata(String(name)).cast<String>(); |
| return ParseMetadataToSchema(metadata); |
| }; |
| // Simple IDs |
| EXPECT_EQ(fetch("testing.schema_id_int"), |
| R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"int"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_float"), |
| R"({"type":"ffi.Function","args":[{"type":"float"},{"type":"float"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_bool"), |
| R"({"type":"ffi.Function","args":[{"type":"bool"},{"type":"bool"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_device"), |
| R"({"type":"ffi.Function","args":[{"type":"Device"},{"type":"Device"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_dtype"), |
| R"({"type":"ffi.Function","args":[{"type":"DataType"},{"type":"DataType"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_string"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.String"},{"type":"ffi.String"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_bytes"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Bytes"},{"type":"ffi.Bytes"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_func"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Function"},{"type":"ffi.Function"}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_func_typed"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Function","args":[{"type":"None"},{"type":"int"},{"type":"float"},{"type":"ffi.Function"}]},{"type":"ffi.Function","args":[{"type":"None"},{"type":"int"},{"type":"float"},{"type":"ffi.Function"}]}]})"); |
| |
| EXPECT_EQ(fetch("testing.schema_id_any"), |
| R"({"type":"ffi.Function","args":[{"type":"Any"},{"type":"Any"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_object"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Object"},{"type":"ffi.Object"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_dltensor"), |
| R"({"type":"ffi.Function","args":[{"type":"DLTensor*"},{"type":"DLTensor*"}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_tensor"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Tensor"},{"type":"ffi.Tensor"}]})"); |
| EXPECT_EQ(fetch("testing.schema_tensor_view_input"), |
| R"({"type":"ffi.Function","args":[{"type":"None"},{"type":"DLTensor*"}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_opt_int"), |
| R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"int"}]},{"type":"Optional","args":[{"type":"int"}]}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_opt_str"), |
| R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"ffi.String"}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_opt_obj"), |
| R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"ffi.Object"}]},{"type":"Optional","args":[{"type":"ffi.Object"}]}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_arr_int"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"ffi.Array","args":[{"type":"int"}]}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_arr_str"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"ffi.String"}]},{"type":"ffi.Array","args":[{"type":"ffi.String"}]}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_arr_obj"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"ffi.Object"}]},{"type":"ffi.Array","args":[{"type":"ffi.Object"}]}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_arr"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Array"},{"type":"ffi.Array"}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_map_str_int"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_map_str_str"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.String"}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.String"}]}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_map_str_obj"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Object"}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Object"}]}]})"); |
| EXPECT_EQ(fetch("testing.schema_id_map"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Map"},{"type":"ffi.Map"}]})"); |
| EXPECT_EQ( |
| fetch("testing.schema_id_variant_int_str"), |
| R"({"type":"ffi.Function","args":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"}]},{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"}]}]})"); |
| |
| // Packed function registered via def_packed: schema is plain ffi.Function |
| EXPECT_EQ(fetch("testing.schema_packed"), R"({"type":"ffi.Function"})"); |
| |
| // Mixed containers and optionals |
| EXPECT_EQ( |
| fetch("testing.schema_arr_map_opt"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"ffi.Array","args":[{"type":"Optional","args":[{"type":"int"}]}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]})"); |
| |
| EXPECT_EQ( |
| fetch("testing.schema_variant_mix"), |
| R"({"type":"ffi.Function","args":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}]})"); |
| |
| // No-arg and no-return combinations |
| EXPECT_EQ(fetch("testing.schema_no_args"), R"({"type":"ffi.Function","args":[{"type":"int"}]})"); |
| EXPECT_EQ(fetch("testing.schema_no_return"), |
| R"({"type":"ffi.Function","args":[{"type":"None"},{"type":"int"}]})"); |
| EXPECT_EQ(fetch("testing.schema_no_args_no_return"), |
| R"({"type":"ffi.Function","args":[{"type":"None"}]})"); |
| } |
| |
| TEST(Schema, FieldTypeSchemas) { |
| // Validate type schema JSON on fields of testing.SchemaAllTypes |
| const char* kTypeKey = "testing.SchemaAllTypes"; |
| // Helper to fetch a field's type schema by name |
| auto field_schema = [&](const char* field_name) -> std::string { |
| const TVMFFIFieldInfo* info = GetFieldInfo(kTypeKey, field_name); |
| return ParseMetadataToSchema(info->metadata); |
| }; |
| |
| EXPECT_EQ(field_schema("v_bool"), R"({"type":"bool"})"); |
| EXPECT_EQ(field_schema("v_int"), R"({"type":"int"})"); |
| EXPECT_EQ(field_schema("v_float"), R"({"type":"float"})"); |
| EXPECT_EQ(field_schema("v_device"), R"({"type":"Device"})"); |
| EXPECT_EQ(field_schema("v_dtype"), R"({"type":"DataType"})"); |
| EXPECT_EQ(field_schema("v_string"), R"({"type":"ffi.String"})"); |
| EXPECT_EQ(field_schema("v_bytes"), R"({"type":"ffi.Bytes"})"); |
| EXPECT_EQ(field_schema("v_opt_int"), R"({"type":"Optional","args":[{"type":"int"}]})"); |
| EXPECT_EQ(field_schema("v_opt_str"), R"({"type":"Optional","args":[{"type":"ffi.String"}]})"); |
| EXPECT_EQ(field_schema("v_arr_int"), R"({"type":"ffi.Array","args":[{"type":"int"}]})"); |
| EXPECT_EQ(field_schema("v_arr_str"), R"({"type":"ffi.Array","args":[{"type":"ffi.String"}]})"); |
| EXPECT_EQ(field_schema("v_map_str_int"), |
| R"({"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]})"); |
| EXPECT_EQ( |
| field_schema("v_map_str_arr_int"), |
| R"({"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]})"); |
| EXPECT_EQ( |
| field_schema("v_variant"), |
| R"({"type":"Variant","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]}]})"); |
| EXPECT_EQ( |
| field_schema("v_opt_arr_variant"), |
| R"({"type":"Optional","args":[{"type":"ffi.Array","args":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"}]}]}]})"); |
| } |
| |
| TEST(Schema, MethodTypeSchemas) { |
| const char* kTypeKey = "testing.SchemaAllTypes"; |
| auto method_schema = [&](const char* method_name) -> std::string { |
| const TVMFFIMethodInfo* info = GetMethodInfo(kTypeKey, method_name); |
| return ParseMetadataToSchema(info->metadata); |
| }; |
| |
| // Instance methods |
| EXPECT_EQ( |
| method_schema("add_int"), |
| R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"testing.SchemaAllTypes"},{"type":"int"}]})"); |
| EXPECT_EQ( |
| method_schema("append_int"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"testing.SchemaAllTypes"},{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"int"}]})"); |
| EXPECT_EQ( |
| method_schema("maybe_concat"), |
| R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"ffi.String"}]},{"type":"testing.SchemaAllTypes"},{"type":"Optional","args":[{"type":"ffi.String"}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]})"); |
| EXPECT_EQ( |
| method_schema("merge_map"), |
| R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"testing.SchemaAllTypes"},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}]})"); |
| |
| // Static method make_with: return type is the object type itself. |
| // Build expected JSON as ffi.Function with return type = type_key and args = (int, float, str) |
| EXPECT_EQ( |
| method_schema("make_with"), |
| R"({"type":"ffi.Function","args":[{"type":"testing.SchemaAllTypes"},{"type":"int"},{"type":"float"},{"type":"ffi.String"}]})"); |
| } |
| |
| TEST(Schema, DLLExportedFuncMetadata) { |
| // Minimal sanity check that DLL export metadata mechanism works. |
| EXPECT_EQ(ParseMetadataToSchema(CallMetadataFunc(__tvm_ffi__metadata_testing_dll_schema_id_int)), |
| R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"int"}]})"); |
| } |
| |
| TEST(Schema, DLLExportedFuncDocumentation) { |
| EXPECT_EQ(ParseMetadataToSchema( |
| CallMetadataFunc(__tvm_ffi__metadata_testing_dll_test_add_with_docstring)), |
| R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"int"},{"type":"int"}]})"); |
| String doc = CallMetadataFunc(__tvm_ffi__doc_testing_dll_test_add_with_docstring); |
| std::string doc_str(doc); |
| EXPECT_EQ(doc_str, |
| R"(Add two integers and return the sum. |
| |
| Parameters |
| ---------- |
| a : int |
| First integer |
| b : int |
| Second integer |
| |
| Returns |
| ------- |
| result : int |
| Sum of a and b)"); |
| } |
| |
| } // namespace |