// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. // // 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 #include #include #include "gtest/gtest.h" #include "test/cpp/utils/exception_test_utils.h" at::Tensor mymuladd_cpu(at::Tensor a, const at::Tensor& b, double c) { TORCH_CHECK(a.sizes() == b.sizes()); TORCH_CHECK(a.dtype() == at::kFloat); TORCH_CHECK(b.dtype() == at::kFloat); TORCH_INTERNAL_ASSERT(a.device().type() == at::DeviceType::CPU); TORCH_INTERNAL_ASSERT(b.device().type() == at::DeviceType::CPU); at::Tensor a_contig = a.contiguous(); at::Tensor b_contig = b.contiguous(); at::Tensor result = torch::empty(a_contig.sizes(), a_contig.options()); const float* a_ptr = a_contig.data_ptr(); const float* b_ptr = b_contig.data_ptr(); float* result_ptr = result.data_ptr(); for (int64_t i = 0; i < result.numel(); i++) { result_ptr[i] = a_ptr[i] * b_ptr[i] + c; } return result; } template T generic_add(T a, T b) { return a + b; } class TestClass : public torch::CustomClassHolder { public: int value; std::string name; TestClass() : value(0), name("default") { std::cout << "TestClass::TestClass() - Default constructor" << std::endl; } TestClass(int v) : value(v), name("single_param") { // NOLINT std::cout << "TestClass::TestClass(int) - Single parameter constructor" << std::endl; } TestClass(int v, const std::string& n) : value(v), name(n) { std::cout << "TestClass::TestClass(int, string) - Double parameters constructor" << std::endl; } int getValue() const { std::cout << "TestClass::getValue() - getter" << std::endl; return value; } const std::string& getName() const { std::cout << "TestClass::getName() - getter" << std::endl; return name; } void setValue(int v) { std::cout << "TestClass::setValue(int) - setter (int)" << std::endl; value = v; } void setName(const std::string& n) { std::cout << "TestClass::setName(string) - setter (string)" << std::endl; name = n; } static int getDefaultValue() { std::cout << "TestClass::getDefaultValue() - static method" << std::endl; return 42; } static int addValues(int a, int b) { std::cout << "TestClass::addValues(int, int) - static method" << std::endl; return a + b; } }; torch::CppFunction MakeKwonlySchemaMethodForTestClass() { torch::CppFunction method( [](const torch::FunctionArgs& args) -> torch::IValue { if (args.has_named_args()) { throw std::runtime_error( "Schema-normalized class method should not receive named args"); } if (args.size() != 3) { throw std::runtime_error("Expected 3 normalized arguments"); } auto instance = args.get>(0); const auto idx_repr = args.get_value(1).is_none() ? std::string("none") : std::to_string(args.get(1)); return torch::IValue(instance->name + "|" + idx_repr + "|" + args.get(2)); }); // The self type is irrelevant here; this test only exercises kwarg // forwarding and schema normalization on the instance-method overload. method.bind_schema(torch::jit::parseSchema( "kwonly_forwarding(Tensor self, *, int? idx=None, str mode=\"nearest\") " "-> str")); return method; } TORCH_LIBRARY(example_library, m) { // Note that "float" in the schema corresponds to the C++ double type // and the Python float type. m.def("mymuladd(Tensor a, Tensor b, float c) -> Tensor"); m.class_("TestClass") .def(torch::init<>()) .def(torch::init()) .def(torch::init()) .def("getValue", &TestClass::getValue) .def("getName", &TestClass::getName) .def("setValue", &TestClass::setValue) .def("setName", &TestClass::setName) .def_static("getDefaultValue", &TestClass::getDefaultValue) .def_static("addValues", &TestClass::addValues); } TEST(test_torch_library, TestLibraryOperators) { auto qualified_name = "example_library::mymuladd"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat))); function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat))); function_args.add_arg(torch::IValue(2.0)); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_tensor()); auto result_tensor = result.get_value().to_tensor(); } TEST(test_torch_library, TestLibraryClasses) { auto qualified_name = "example_library::TestClass"; const auto& class_registry = torch::ClassRegistry::instance(); bool has_class = class_registry.has_class(qualified_name); ASSERT_TRUE(has_class); torch::FunctionArgs constructor_args; constructor_args.add_arg(torch::IValue(10)); constructor_args.add_arg(torch::IValue("example")); // Call constructor auto instance = class_registry.call_constructor_with_args(qualified_name, constructor_args); ASSERT_TRUE(instance.get_value().is_custom_class()); // Call getValue auto get_value_result = class_registry.call_method_with_args( qualified_name, "getValue", instance.get_value(), torch::FunctionArgs()); ASSERT_TRUE(get_value_result.get_value().is_int()); int value = get_value_result.get_value().to_int(); ASSERT_EQ(value, 10); // Call setValue torch::FunctionArgs set_value_args; set_value_args.add_arg(torch::IValue(20)); class_registry.call_method_with_args( qualified_name, "setValue", instance.get_value(), set_value_args); ASSERT_EQ(instance.get_value().to_custom_class()->value, 20); auto get_value_after_set = class_registry.call_method_with_args( qualified_name, "getValue", instance.get_value(), torch::FunctionArgs()); ASSERT_EQ(get_value_after_set.get_value().to_int(), 20); // Call getName auto get_name_result = class_registry.call_method_with_args( qualified_name, "getName", instance.get_value(), torch::FunctionArgs()); ASSERT_TRUE(get_name_result.get_value().is_string()); std::string name = get_name_result.get_value().to_string(); ASSERT_EQ(name, "example"); // Call setName torch::FunctionArgs set_name_args; set_name_args.add_arg(torch::IValue("new_example")); class_registry.call_method_with_args( qualified_name, "setName", instance.get_value(), set_name_args); ASSERT_EQ(instance.get_value().to_custom_class()->name, "new_example"); auto get_name_after_set = class_registry.call_method_with_args( qualified_name, "getName", instance.get_value(), torch::FunctionArgs()); ASSERT_EQ(get_name_after_set.get_value().to_string(), "new_example"); // Call static method getDefaultValue auto get_default_value_result = class_registry.call_static_method_with_args( qualified_name, "getDefaultValue", torch::FunctionArgs()); ASSERT_TRUE(get_default_value_result.get_value().is_int()); int default_value = get_default_value_result.get_value().to_int(); ASSERT_EQ(default_value, 42); // Call static method addValues torch::FunctionArgs add_values_args; add_values_args.add_arg(torch::IValue(5)); add_values_args.add_arg(torch::IValue(7)); auto add_values_result = class_registry.call_static_method_with_args( qualified_name, "addValues", add_values_args); ASSERT_TRUE(add_values_result.get_value().is_int()); int sum = add_values_result.get_value().to_int(); ASSERT_EQ(sum, 12); } TORCH_LIBRARY_IMPL(example_library, CPU, m) { m.impl("mymuladd", &mymuladd_cpu); } TORCH_LIBRARY_FRAGMENT(example_library_fragment, m) { m.def("int_add", &generic_add); } TORCH_LIBRARY_FRAGMENT(example_library_fragment, m) { m.def("string_concat", &generic_add); } TEST(test_torch_library, TestFragmentOperators) { auto qualified_name_int_add = "example_library_fragment::int_add"; auto* op_int_add = torch::OperatorRegistry::instance().find_operator(qualified_name_int_add); ASSERT_NE(op_int_add, nullptr); auto impl_it_int_add = op_int_add->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it_int_add, op_int_add->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(3)); function_args.add_arg(torch::IValue(4)); auto result = impl_it_int_add->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_int()); int sum = result.get_value().to_int(); ASSERT_EQ(sum, 7); auto qualified_name_string_concat = "example_library_fragment::string_concat"; auto* op_string_concat = torch::OperatorRegistry::instance().find_operator( qualified_name_string_concat); ASSERT_NE(op_string_concat, nullptr); auto impl_it_string_concat = op_string_concat->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it_string_concat, op_string_concat->implementations.end()); torch::FunctionArgs string_args; string_args.add_arg(torch::IValue(std::string("Hello, "))); string_args.add_arg(torch::IValue(std::string("World!"))); auto string_result = impl_it_string_concat->second.call_with_args(string_args); ASSERT_TRUE(string_result.get_value().is_string()); std::string concatenated_string = string_result.get_value().to_string(); ASSERT_EQ(concatenated_string, "Hello, World!"); } int schema_only_add(int a, int b) { return a + b; } int schema_and_impl_add(int a, int b) { return a + b; } int name_only_add(int a, int b) { return a + b; } int overload_name_add(int a, int b) { return a + b; } int dispatch_probe_cpu(int x) { return x + 1; } int dispatch_probe_cuda(int x) { return x + 2; } int impl_block_schema_and_fn(int x) { return x * 2; } TORCH_LIBRARY(example_library_with_mdef_cases, m) { m.def("schema_only_add(int a, int b) -> int"); m.def("schema_and_impl_add(int a, int b) -> int", &schema_and_impl_add); m.def("name_only_add", &name_only_add); m.def("schema_only_no_impl(int x) -> int"); m.def("overload.name(int a, int b) -> int", &overload_name_add); m.def("dispatch_probe(int x) -> int"); } TORCH_LIBRARY_IMPL(example_library_with_mdef_cases, CPU, m) { m.impl("schema_only_add", &schema_only_add); m.impl("dispatch_probe", &dispatch_probe_cpu); } TORCH_LIBRARY_IMPL(example_library_with_mdef_cases, CUDA, m) { m.impl("dispatch_probe", &dispatch_probe_cuda); } TORCH_LIBRARY_IMPL(example_library_mdef_impl_block, CPU, m) { // def() in IMPL block is explicitly ignored. m.def("impl_block_schema_only(int x) -> int"); m.def("impl_block_schema_and_fn(int x) -> int", &impl_block_schema_and_fn); } at::Tensor add_scalar_to_float_tensor(const at::Tensor& input, double value) { at::Tensor in_contig = input.contiguous(); at::Tensor output = at::empty(in_contig.sizes(), in_contig.options()); const float* in_ptr = in_contig.data_ptr(); float* out_ptr = output.data_ptr(); for (int64_t idx = 0; idx < output.numel(); ++idx) { out_ptr[idx] = in_ptr[idx] + static_cast(value); } return output; } at::Tensor mdef_schema_matrix_basic_types(const at::Tensor& x, int i, double f, bool b, const std::string& s, const std::string& d, double n, std::optional z) { const double bias = static_cast(i) + f + (b ? 1.0 : 0.0) + static_cast(s.size()) + static_cast(d.size()) + n + static_cast(z.value_or(0)); return add_scalar_to_float_tensor(x, bias); } double mdef_schema_matrix_number_aliases(double a, double b) { return a + b; } std::tuple mdef_schema_matrix_optional_types( std::optional i, std::optional f, std::optional b, std::optional s, std::optional t) { const int64_t score = i.value_or(0) + static_cast(f.value_or(0.0)) + (b.value_or(false) ? 1 : 0) + static_cast(s ? s->size() : 0); at::Tensor base = t.has_value() ? *t : at::zeros({1}, at::kFloat); return {add_scalar_to_float_tensor(base, static_cast(score)), score}; } std::tuple mdef_schema_matrix_tuple_optional( std::optional> payload) { if (!payload.has_value()) { return {at::zeros({1}, at::kFloat), at::ones({1}, at::kFloat)}; } const auto& [x, i, f, b, s] = *payload; const double rhs = static_cast(i) + f + (b ? 1.0 : 0.0) + s.size(); return {x, add_scalar_to_float_tensor(x, rhs)}; } std::string mdef_schema_matrix_defaults_mix(int i, double f, bool b, const std::string& quoted, const std::string& ident) { return std::to_string(i) + "|" + std::to_string(static_cast(f * 10.0)) + "|" + (b ? "1" : "0") + "|" + quoted + "|" + ident; } void mdef_schema_matrix_alias_and_kwonly(const at::Tensor& x, std::optional idx, const std::string& mode) { if (idx.has_value()) { (void)x[idx.value()]; } (void)mode; } TORCH_LIBRARY(example_library_mdef_schema_matrix, m) { m.def( "basic_types(Tensor x, int i, float f, bool b, str s, Device d, Scalar " "n, NoneType z=None) -> Tensor", &mdef_schema_matrix_basic_types); m.def("number_aliases(Scalar a, number b) -> Scalar", &mdef_schema_matrix_number_aliases); m.def( "optional_types(int? i=None, float? f=None, bool? b=None, str? s=None, " "Tensor? t=None) -> (Tensor, int)", &mdef_schema_matrix_optional_types); m.def( "tuple_optional((Tensor, int, float, bool, str)? payload=None) -> " "(Tensor, Tensor)", &mdef_schema_matrix_tuple_optional); m.def( "defaults_mix(int i=3, float f=-2.5, bool b=true, str quoted=\"abc\", " "str ident=cpu) -> str", &mdef_schema_matrix_defaults_mix); m.def( "alias_and_kwonly(Tensor(a!) x, *, int? idx=None, str mode=\"nearest\") " "-> ()", &mdef_schema_matrix_alias_and_kwonly); m.def("variadic_signature(Tensor x, ...) -> ...", [](const torch::FunctionArgs& args) -> torch::IValue { int64_t sum = 0; for (size_t i = 1; i < args.size(); ++i) { sum += args.get(i); } return torch::IValue(sum); }); } TEST(test_torch_library, TestMDefRegistrationPathsCallResult) { struct CallCase { const char* qualified_name; std::vector args; int64_t expected; }; const std::vector cases = { {"example_library_with_mdef_cases::schema_only_add", {torch::IValue(11), torch::IValue(31)}, 42}, {"example_library_with_mdef_cases::schema_and_impl_add", {torch::IValue(19), torch::IValue(23)}, 42}, {"example_library_with_mdef_cases::name_only_add", {torch::IValue(20), torch::IValue(22)}, 42}, // Dotted overload-style names should preserve suffix before '('. {"example_library_with_mdef_cases::overload.name", {torch::IValue(40), torch::IValue(2)}, 42}, }; for (const auto& test_case : cases) { SCOPED_TRACE(test_case.qualified_name); auto* op = torch::OperatorRegistry::instance().find_operator( test_case.qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; for (const auto& arg : test_case.args) { function_args.add_arg(arg); } auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_int()); EXPECT_EQ(result.get_value().to_int(), test_case.expected); } } TEST(test_torch_library, TestMDefSchemaOnlyWithoutImplHasNoImplementation) { auto qualified_name = "example_library_with_mdef_cases::schema_only_no_impl"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); EXPECT_TRUE(op->implementations.empty()); } TEST(test_torch_library, TestMDefRegistersMultipleDispatchImplementations) { auto qualified_name = "example_library_with_mdef_cases::dispatch_probe"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto cpu_it = op->implementations.find(torch::DispatchKey::CPU); auto cuda_it = op->implementations.find(torch::DispatchKey::CUDA); ASSERT_NE(cpu_it, op->implementations.end()); ASSERT_NE(cuda_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(41)); auto cpu_result = cpu_it->second.call_with_args(function_args); auto cuda_result = cuda_it->second.call_with_args(function_args); ASSERT_TRUE(cpu_result.get_value().is_int()); ASSERT_TRUE(cuda_result.get_value().is_int()); EXPECT_EQ(cpu_result.get_value().to_int(), 42); EXPECT_EQ(cuda_result.get_value().to_int(), 43); } TEST(test_torch_library, TestMDefInImplBlockIsNoop) { { auto qualified_name = "example_library_mdef_impl_block::impl_block_schema_only"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); EXPECT_EQ(op, nullptr); } { auto qualified_name = "example_library_mdef_impl_block::impl_block_schema_and_fn"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); EXPECT_EQ(op, nullptr); } } TEST(test_torch_library, TestMDefSchemaMatrixBasicTypesCallResult) { auto qualified_name = "example_library_mdef_schema_matrix::basic_types"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat))); function_args.add_arg(torch::IValue(1)); function_args.add_arg(torch::IValue(2.5)); function_args.add_arg(torch::IValue(true)); function_args.add_arg(torch::IValue(std::string("ab"))); function_args.add_arg(torch::IValue(std::string("cpu"))); function_args.add_arg(torch::IValue(3.5)); function_args.add_arg(torch::IValue(int64_t(4))); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_tensor()); auto out = result.get_value().to_tensor(); EXPECT_EQ(out.sizes(), at::IntArrayRef({2, 2})); EXPECT_FLOAT_EQ(out[0][0].item(), 18.0f); } TEST(test_torch_library, TestMDefSchemaMatrixNumberAliasesCallResult) { auto qualified_name = "example_library_mdef_schema_matrix::number_aliases"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(19.5)); function_args.add_arg(torch::IValue(22.5)); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_double()); EXPECT_DOUBLE_EQ(result.get_value().to_double(), 42.0); } TEST(test_torch_library, TestMDefSchemaMatrixOptionalAndTupleCallResult) { { auto qualified_name = "example_library_mdef_schema_matrix::optional_types"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs args_with_values; args_with_values.add_arg(torch::IValue(int64_t(5))); args_with_values.add_arg(torch::IValue(2.0)); args_with_values.add_arg(torch::IValue(true)); args_with_values.add_arg(torch::IValue(std::string("abc"))); args_with_values.add_arg(torch::IValue(at::ones({1}, at::kFloat))); auto result = impl_it->second.call_with_args(args_with_values); ASSERT_TRUE(result.get_value().is_tuple()); const auto tuple_val = result.get_value().to_tuple(); ASSERT_EQ(tuple_val.size(), 2UL); EXPECT_FLOAT_EQ(tuple_val[0].to_tensor()[0].item(), 12.0f); EXPECT_EQ(tuple_val[1].to_int(), 11); } { auto qualified_name = "example_library_mdef_schema_matrix::tuple_optional"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs args_with_payload; args_with_payload.add_arg(torch::IValue(std::make_tuple( at::ones({1}, at::kFloat), int64_t(2), 3.0, true, std::string("ab")))); auto result = impl_it->second.call_with_args(args_with_payload); ASSERT_TRUE(result.get_value().is_tuple()); const auto tuple_val = result.get_value().to_tuple(); ASSERT_EQ(tuple_val.size(), 2UL); EXPECT_FLOAT_EQ(tuple_val[0].to_tensor()[0].item(), 1.0f); EXPECT_FLOAT_EQ(tuple_val[1].to_tensor()[0].item(), 9.0f); } } TEST(test_torch_library, TestMDefSchemaMatrixDefaultsAliasAndVariadicCallResult) { { auto qualified_name = "example_library_mdef_schema_matrix::defaults_mix"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(3)); function_args.add_arg(torch::IValue(-2.5)); function_args.add_arg(torch::IValue(true)); function_args.add_arg(torch::IValue(std::string("abc"))); function_args.add_arg(torch::IValue(std::string("cpu"))); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_string()); EXPECT_EQ(result.get_value().to_string(), "3|-25|1|abc|cpu"); } { auto qualified_name = "example_library_mdef_schema_matrix::variadic_signature"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(at::zeros({1}, at::kFloat))); function_args.add_arg(torch::IValue(10)); function_args.add_arg(torch::IValue(20)); function_args.add_arg(torch::IValue(12)); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_int()); EXPECT_EQ(result.get_value().to_int(), 42); } } TEST(test_torch_library, TestMDefKeywordOnlyCallBehavior) { auto qualified_name = "example_library_mdef_schema_matrix::alias_and_kwonly"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); { torch::FunctionArgs args_with_optional_none; args_with_optional_none.add_arg(torch::IValue(at::ones({4}, at::kFloat))); args_with_optional_none.add_arg(torch::arg("idx") = torch::IValue()); args_with_optional_none.add_arg(torch::arg("mode") = "nearest"); auto result = impl_it->second.call_with_args(args_with_optional_none); EXPECT_TRUE(result.get_value().is_none()); } { torch::FunctionArgs args_with_defaults; args_with_defaults.add_arg(torch::IValue(at::ones({4}, at::kFloat))); auto result = impl_it->second.call_with_args(args_with_defaults); EXPECT_TRUE(result.get_value().is_none()); } { torch::FunctionArgs positional_kwonly_args; positional_kwonly_args.add_arg(torch::IValue(at::ones({4}, at::kFloat))); positional_kwonly_args.add_arg(torch::IValue(int64_t(2))); EXPECT_ANY_THROW( (void)impl_it->second.call_with_args(positional_kwonly_args)); } } TEST(test_torch_library, TestFunctionArgsRejectsDuplicateKeywordArgument) { torch::FunctionArgs function_args; function_args.add_arg(torch::arg("idx") = int64_t(1)); test::utils::ExpectThrowContains( [&]() { function_args.add_arg(torch::arg("idx") = int64_t(2)); }, "Duplicate keyword argument `idx`"); } TEST(test_torch_library, TestFunctionArgsAdditionalBranches) { torch::FunctionArgs args; EXPECT_THROW(args.add_arg(torch::arg("missing")), std::runtime_error); args.add_arg("cpu"); args.add_arg(torch::IValue(int64_t(7))); args.add_arg(int64_t(3)); args.add_arg(torch::arg("mode") = "nearest"); args.add_arg(torch::arg("idx") = int64_t(2)); ASSERT_EQ(args.size(), 3UL); ASSERT_EQ(args.named_size(), 2UL); EXPECT_TRUE(args.has_named_args()); EXPECT_FALSE(args.empty()); EXPECT_EQ(args.get(0), "cpu"); const int64_t& ref_value = args.get(1); const int64_t const_value = args.get(2); EXPECT_EQ(ref_value, 7); EXPECT_EQ(const_value, 3); const auto args_text = args.to_string(); EXPECT_NE(args_text.find("kwargs={"), std::string::npos); EXPECT_NE(args_text.find("mode"), std::string::npos); EXPECT_NE(args_text.find("idx"), std::string::npos); auto from_vector = torch::FunctionArgs::from_vector( std::vector{torch::IValue(int64_t(11))}); EXPECT_EQ(from_vector.get(0), 11); } TEST(test_torch_library, TestFunctionArgsErrorBranches) { torch::FunctionArgs args; args.add_arg(torch::IValue(int64_t(1))); EXPECT_THROW((void)args.get(0), std::runtime_error); EXPECT_THROW((void)args.get_value(1), std::out_of_range); test::utils::ExpectThrowContains( [&]() { (void)args.to_tuple(); }, "Argument count mismatch"); } TEST(test_torch_library, TestFunctionResultErrorBranches) { torch::FunctionResult empty_result; EXPECT_FALSE(empty_result.has_value()); EXPECT_THROW((void)empty_result.get(), std::runtime_error); torch::FunctionResult string_result(torch::IValue(std::string("abc"))); EXPECT_THROW((void)string_result.get(), std::runtime_error); } TEST(test_torch_library, TestCppFunctionWrapperAndUninitializedErrors) { torch::CppFunction uninitialized; EXPECT_FALSE(uninitialized.valid()); EXPECT_THROW((void)uninitialized.call(), std::runtime_error); EXPECT_THROW((void)uninitialized.call(1), std::runtime_error); EXPECT_THROW((void)uninitialized.call_with_args(torch::FunctionArgs()), std::runtime_error); std::function ctor_thrower = [](const torch::FunctionArgs& args) -> torch::IValue { (void)args; throw std::runtime_error("boom_ctor"); }; torch::CppFunction ctor_wrapped(ctor_thrower); test::utils::ExpectThrowContains( [&]() { (void)ctor_wrapped.call_with_args(torch::FunctionArgs()); }, "Constructor failed: boom_ctor"); auto throw_in_free_function = +[](int x) -> int { (void)x; throw std::runtime_error("boom_fn"); }; torch::CppFunction free_fn_wrapped(throw_in_free_function); torch::FunctionArgs single_arg; single_arg.add_arg(torch::IValue(int64_t(1))); test::utils::ExpectThrowContains( [&]() { (void)free_fn_wrapped.call_with_args(single_arg); }, "Function call failed: boom_fn"); auto throwing_callable = [](const torch::FunctionArgs& args) -> torch::IValue { (void)args; throw std::runtime_error("boom_lambda"); }; torch::CppFunction lambda_wrapped(throwing_callable); test::utils::ExpectThrowContains( [&]() { (void)lambda_wrapped.call_with_args(torch::FunctionArgs()); }, "Lambda execution failed: boom_lambda"); } TEST(test_torch_library, TestCppFunctionSchemaNormalizationErrorBranches) { { torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue { return torch::IValue(args.get(0) + args.get(1)); }); fn.bind_schema(torch::jit::parseSchema("normalize(int a, int b=1) -> int")); torch::FunctionArgs too_many_positional; too_many_positional.add_arg(torch::IValue(int64_t(1))); too_many_positional.add_arg(torch::IValue(int64_t(2))); too_many_positional.add_arg(torch::IValue(int64_t(3))); test::utils::ExpectThrowContains( [&]() { (void)fn.call_with_args(too_many_positional); }, "Too many positional arguments"); } { torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue { return torch::IValue(args.get(0) + args.get(1)); }); fn.bind_schema( torch::jit::parseSchema("normalize_kw(int a, *, int b=1) -> int")); torch::FunctionArgs positional_kwonly; positional_kwonly.add_arg(torch::IValue(int64_t(1))); positional_kwonly.add_arg(torch::IValue(int64_t(2))); test::utils::ExpectThrowContains( [&]() { (void)fn.call_with_args(positional_kwonly); }, "keyword-only"); torch::FunctionArgs unknown_kw; unknown_kw.add_arg(torch::IValue(int64_t(1))); unknown_kw.add_arg(torch::arg("unknown") = int64_t(2)); test::utils::ExpectThrowContains( [&]() { (void)fn.call_with_args(unknown_kw); }, "Unknown keyword argument `unknown`"); } { torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue { return torch::IValue(args.get(0) + args.get(1)); }); fn.bind_schema( torch::jit::parseSchema("normalize_dup(int a, int b) -> int")); torch::FunctionArgs duplicated; duplicated.add_arg(torch::IValue(int64_t(1))); duplicated.add_arg(torch::IValue(int64_t(2))); duplicated.add_arg(torch::arg("b") = int64_t(3)); test::utils::ExpectThrowContains( [&]() { (void)fn.call_with_args(duplicated); }, "already provided"); } { torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue { return torch::IValue(args.get(0) + args.get(1)); }); fn.bind_schema( torch::jit::parseSchema("normalize_missing(int a, int b) -> int")); torch::FunctionArgs missing_required; missing_required.add_arg(torch::IValue(int64_t(1))); test::utils::ExpectThrowContains( [&]() { (void)fn.call_with_args(missing_required); }, "Missing required argument `b`"); } } TEST(test_torch_library, TestCppFunctionSchemaNormalizationVarargPassthrough) { torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue { int64_t sum = 0; for (size_t i = 0; i < args.size(); ++i) { sum += args.get(i); } return torch::IValue(sum); }); fn.bind_schema( torch::jit::parseSchema("normalize_vararg(int a, ...) -> int")); torch::FunctionArgs inputs; inputs.add_arg(torch::IValue(int64_t(1))); inputs.add_arg(torch::IValue(int64_t(2))); inputs.add_arg(torch::IValue(int64_t(3))); auto result = fn.call_with_args(inputs); ASSERT_TRUE(result.get_value().is_int()); EXPECT_EQ(result.get_value().to_int(), 6); } TEST(test_torch_library, TestCppFunctionArityMismatchFromFunctionTraits) { torch::CppFunction add_two_ints(&schema_only_add); torch::FunctionArgs missing_one; missing_one.add_arg(torch::IValue(int64_t(1))); test::utils::ExpectThrowContains( [&]() { (void)add_two_ints.call_with_args(missing_one); }, "Function expects 2 arguments, got 1"); } TEST(test_torch_library, TestClassMethodArityMismatchFromFunctionTraits) { auto qualified_name = "example_library::TestClass"; const auto& class_registry = torch::ClassRegistry::instance(); torch::FunctionArgs constructor_args; constructor_args.add_arg(torch::IValue(10)); constructor_args.add_arg(torch::IValue("example")); auto instance = class_registry.call_constructor_with_args(qualified_name, constructor_args); test::utils::ExpectThrowContains( [&]() { (void)class_registry.call_method_with_args(qualified_name, "setValue", instance.get_value(), torch::FunctionArgs()); }, "Method expects 1 arguments"); } TEST(test_torch_library, TestClassMethodKwonlyArgsForwardedThroughInstanceOverload) { auto qualified_name = "example_library::TestClass"; auto method_name = "kwonlyForwarding"; auto& class_registry = torch::ClassRegistry::instance(); class_registry.register_method( qualified_name, method_name, MakeKwonlySchemaMethodForTestClass()); torch::FunctionArgs constructor_args; constructor_args.add_arg(torch::IValue(10)); constructor_args.add_arg(torch::IValue("example")); auto instance = class_registry.call_constructor_with_args(qualified_name, constructor_args); { torch::FunctionArgs kwonly_args; kwonly_args.add_arg(torch::arg("idx") = int64_t(7)); kwonly_args.add_arg(torch::arg("mode") = "linear"); auto result = class_registry.call_method_with_args( qualified_name, method_name, instance.get_value(), kwonly_args); ASSERT_TRUE(result.get_value().is_string()); EXPECT_EQ(result.get_value().to_string(), "example|7|linear"); } { torch::FunctionArgs positional_kwonly_args; positional_kwonly_args.add_arg(torch::IValue(int64_t(7))); test::utils::ExpectThrowContains( [&]() { (void)class_registry.call_method_with_args(qualified_name, method_name, instance.get_value(), positional_kwonly_args); }, "keyword-only"); } } TEST(test_torch_library, TestMDefSchemaDefaultsAppliedByCallWithArgs) { auto qualified_name = "example_library_mdef_schema_matrix::defaults_mix"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs args_without_values; auto result = impl_it->second.call_with_args(args_without_values); ASSERT_TRUE(result.get_value().is_string()); EXPECT_EQ(result.get_value().to_string(), "3|-25|1|abc|cpu"); } at::Tensor cast_with_scalar_type(at::Tensor input, c10::ScalarType dtype) { return input.toType(dtype); } TORCH_LIBRARY(example_library_with_scalar_type_input, m) { m.def("cast_with_scalar_type", &cast_with_scalar_type); } TEST(test_torch_library, TestScalarTypeInput) { auto qualified_name = "example_library_with_scalar_type_input::cast_with_scalar_type"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat))); function_args.add_arg(torch::IValue(at::kDouble)); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_tensor()); auto result_tensor = result.get_value().to_tensor(); ASSERT_EQ(result_tensor.dtype(), at::kDouble); } TEST(test_torch_library, TestRegisterImplementationAtRuntime) { auto qualified_name = "runtime_example::runtime_add"; auto& registry = torch::OperatorRegistry::instance(); registry.register_implementation(qualified_name, c10::DispatchKey::CPU, torch::CppFunction(&generic_add)); auto* op = registry.find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(11)); function_args.add_arg(torch::IValue(31)); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_int()); ASSERT_EQ(result.get_value().to_int(), 42); } TEST(test_torch_library, TestLibraryPrintInfoWithDispatchKey) { torch::Library library(torch::Library::IMPL, "runtime_library_info", std::make_optional(c10::DispatchKey::CPU), __FILE__, __LINE__); testing::internal::CaptureStdout(); library.print_info(); auto output = testing::internal::GetCapturedStdout(); ASSERT_NE(output.find("Library Info: IMPL"), std::string::npos); ASSERT_NE(output.find("namespace=runtime_library_info"), std::string::npos); ASSERT_NE(output.find("dispatch_key="), std::string::npos); } int fn_with_int_const(int const x) { return x + 1; } TORCH_LIBRARY(example_library_with_int_const, m) { m.def("fn_with_int_const", &fn_with_int_const); } TEST(test_torch_library, TestIntConst) { auto qualified_name = "example_library_with_int_const::fn_with_int_const"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(3)); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_int()); int value = result.get_value().to_int(); ASSERT_EQ(value, 4); } int fn_with_optional_input(torch::optional x) { if (x.has_value()) { return x.value() + 1; } else { return -1; } } TORCH_LIBRARY(example_library_with_optional_input, m) { m.def("fn_with_optional_input", &fn_with_optional_input); } TEST(test_torch_library, TestOptionalInput) { auto qualified_name = "example_library_with_optional_input::fn_with_optional_input"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); // Test with value torch::FunctionArgs function_args_with_value; function_args_with_value.add_arg(torch::IValue(int64_t(5))); auto result_with_value = impl_it->second.call_with_args(function_args_with_value); ASSERT_TRUE(result_with_value.get_value().is_int()); int value_with_value = result_with_value.get_value().to_int(); ASSERT_EQ(value_with_value, 6); // Test without value (nullopt) torch::FunctionArgs function_args_without_value; function_args_without_value.add_arg(torch::IValue()); auto result_without_value = impl_it->second.call_with_args(function_args_without_value); ASSERT_TRUE(result_without_value.get_value().is_int()); int value_without_value = result_without_value.get_value().to_int(); ASSERT_EQ(value_without_value, -1); } int fn_with_arrayref_input(c10::ArrayRef x) { int sum = 0; for (const auto& val : x) { sum += val; } return sum; } TORCH_LIBRARY(example_library_with_arrayref_input, m) { m.def("fn_with_arrayref_input", &fn_with_arrayref_input); } TEST(test_torch_library, TestArrayRefInput) { auto qualified_name = "example_library_with_arrayref_input::fn_with_arrayref_input"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(std::vector({1, 2, 3, 4}))); auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_int()); int value = result.get_value().to_int(); ASSERT_EQ(value, 10); } int fn_with_mix_optional_arrayref_input( c10::optional> x) { if (x.has_value()) { int sum = 0; for (const auto& val : x.value()) { sum += val; } return sum; } else { return -1; } } TORCH_LIBRARY(example_library_with_mix_optional_arrayref_input, m) { m.def("fn_with_mix_optional_arrayref_input", &fn_with_mix_optional_arrayref_input); } TEST(test_torch_library, TestMixOptionalArrayRefInput) { auto qualified_name = "example_library_with_mix_optional_arrayref_input::" "fn_with_mix_optional_arrayref_input"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); // Test with value torch::FunctionArgs function_args_with_value; function_args_with_value.add_arg( torch::IValue(std::vector({1, 2, 3, 4}))); auto result_with_value = impl_it->second.call_with_args(function_args_with_value); ASSERT_TRUE(result_with_value.get_value().is_int()); int value_with_value = result_with_value.get_value().to_int(); ASSERT_EQ(value_with_value, 10); // Test without value (nullopt) torch::FunctionArgs function_args_without_value; function_args_without_value.add_arg(torch::IValue()); auto result_without_value = impl_it->second.call_with_args(function_args_without_value); ASSERT_TRUE(result_without_value.get_value().is_int()); int value_without_value = result_without_value.get_value().to_int(); ASSERT_EQ(value_without_value, -1); } void fn_with_optional_tensor_const_ref_input( torch::optional const& x) {} TORCH_LIBRARY(example_library_with_optional_tensor_const_ref_input, m) { m.def("fn_with_optional_tensor_const_ref_input", &fn_with_optional_tensor_const_ref_input); } TEST(test_torch_library, TestOptionalTensorConstRefInput) { auto qualified_name = "example_library_with_optional_tensor_const_ref_input::" "fn_with_optional_tensor_const_ref_input"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); // Test with value torch::FunctionArgs function_args_with_value; function_args_with_value.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat))); impl_it->second.call_with_args(function_args_with_value); // Test without value (nullopt) torch::FunctionArgs function_args_without_value; function_args_without_value.add_arg(torch::IValue()); impl_it->second.call_with_args(function_args_without_value); } // Function that returns a list of two tensors (instead of tuple) std::vector return_tensor_list(const at::Tensor& input, int dim) { // Simply create two tensors of different sizes as demonstration auto first_part = at::ones({2}, input.options()); auto second_part = at::ones({2}, input.options()); return {first_part, second_part}; } // Function that actually returns std::tuple std::tuple return_tensor_tuple(const at::Tensor& input, int dim) { // Create two tensors and return as tuple auto first_part = at::ones({2}, input.options()); auto second_part = at::ones({3}, input.options()); // Different size to verify return std::make_tuple(first_part, second_part); } // Function that actually returns std::tuple std::tuple return_tensor_tuple_3( const at::Tensor& input, int dim) { // Create two tensors and return as tuple auto first_part = at::ones({2}, input.options()); auto second_part = at::ones({3}, input.options()); // Different size to verify auto third_part = at::ones({4}, input.options()); return std::make_tuple(first_part, second_part, third_part); } TORCH_LIBRARY(example_library_with_tuple_return, m) { m.def("split_tensor_list", &return_tensor_list); m.def("split_tensor_tuple", &return_tensor_tuple); m.def("split_tensor_tuple_3", &return_tensor_tuple_3); } TEST(test_torch_library, TestTupleReturn) { // Test vector return (list) auto qualified_name_list = "example_library_with_tuple_return::split_tensor_list"; auto* op_list = torch::OperatorRegistry::instance().find_operator(qualified_name_list); ASSERT_NE(op_list, nullptr); auto impl_it_list = op_list->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it_list, op_list->implementations.end()); // Create a test tensor [0, 1, 2, 3] with shape [4] std::vector data = {0.0f, 1.0f, 2.0f, 3.0f}; auto input_tensor = at::from_blob(data.data(), {4}, at::kFloat).clone(); torch::FunctionArgs function_args_list; function_args_list.add_arg(torch::IValue(input_tensor)); function_args_list.add_arg(torch::IValue(0)); // split along dimension 0 auto result_list = impl_it_list->second.call_with_args(function_args_list); // Verify the result is a GenericList (vector of tensors) ASSERT_TRUE(result_list.get_value().is_list()); auto list_val = result_list.get_value().to_list(); ASSERT_EQ(list_val.size(), 2); // Check first tensor should have size [2] auto first_tensor_list = list_val[0].to_tensor(); ASSERT_EQ(first_tensor_list.size(0), 2); // Check second tensor should have size [2] auto second_tensor_list = list_val[1].to_tensor(); ASSERT_EQ(second_tensor_list.size(0), 2); // Test std::tuple return (tuple) auto qualified_name_tuple = "example_library_with_tuple_return::split_tensor_tuple"; auto* op_tuple = torch::OperatorRegistry::instance().find_operator(qualified_name_tuple); ASSERT_NE(op_tuple, nullptr); auto impl_it_tuple = op_tuple->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it_tuple, op_tuple->implementations.end()); torch::FunctionArgs function_args_tuple; function_args_tuple.add_arg(torch::IValue(input_tensor)); function_args_tuple.add_arg(torch::IValue(0)); // split along dimension 0 auto result_tuple = impl_it_tuple->second.call_with_args(function_args_tuple); // Verify the result is a tuple ASSERT_TRUE(result_tuple.get_value().is_tuple()); auto tuple_val = result_tuple.get_value().to_tuple(); ASSERT_EQ(tuple_val.size(), 2); // Check first tensor should have size [2] auto first_tensor_tuple = tuple_val[0].to_tensor(); ASSERT_EQ(first_tensor_tuple.size(0), 2); // Check second tensor should have size [3] (different from first) auto second_tensor_tuple = tuple_val[1].to_tensor(); ASSERT_EQ(second_tensor_tuple.size(0), 3); // Test std::tuple return (tuple) auto qualified_name_tuple_3 = "example_library_with_tuple_return::split_tensor_tuple_3"; auto* op_tuple_3 = torch::OperatorRegistry::instance().find_operator(qualified_name_tuple_3); ASSERT_NE(op_tuple_3, nullptr); auto impl_it_tuple_3 = op_tuple_3->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it_tuple_3, op_tuple_3->implementations.end()); torch::FunctionArgs function_args_tuple_3; function_args_tuple_3.add_arg(torch::IValue(input_tensor)); function_args_tuple_3.add_arg(torch::IValue(0)); // split along dimension 0 auto result_tuple_3 = impl_it_tuple_3->second.call_with_args(function_args_tuple_3); // Verify the result is a tuple ASSERT_TRUE(result_tuple_3.get_value().is_tuple()); auto tuple_val_3 = result_tuple_3.get_value().to_tuple(); ASSERT_EQ(tuple_val_3.size(), 3); // Check first tensor should have size [2] auto first_tensor_tuple_3 = tuple_val_3[0].to_tensor(); ASSERT_EQ(first_tensor_tuple_3.size(0), 2); // Check second tensor should have size [3] (different from first) auto second_tensor_tuple_3 = tuple_val_3[1].to_tensor(); ASSERT_EQ(second_tensor_tuple_3.size(0), 3); // Check third tensor should have size [4] (different from first and second) auto third_tensor_tuple_3 = tuple_val_3[2].to_tensor(); ASSERT_EQ(third_tensor_tuple_3.size(0), 4); } // Test for const reference parameters fix void fn_with_const_ref_param(const int& x, const std::string& str) { // Simple function to test const reference parameter handling } TORCH_LIBRARY(example_library_const_ref_fix, m) { m.def("fn_with_const_ref_param", &fn_with_const_ref_param); } TEST(test_torch_library, TestConstRefParameterFix) { auto qualified_name = "example_library_const_ref_fix::fn_with_const_ref_param"; auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(c10::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); // Test with const reference parameters torch::FunctionArgs function_args; function_args.add_arg(torch::IValue(42)); function_args.add_arg(torch::IValue(std::string("test"))); // This should not throw compilation errors auto result = impl_it->second.call_with_args(function_args); ASSERT_TRUE(result.get_value().is_none()); // void function returns None } TEST(test_torch_library, TestClassRegistryHasNonExistentClass) { auto qualified_name = "example_library::NonExistentClass"; const auto& class_registry = torch::ClassRegistry::instance(); bool has_class = class_registry.has_class(qualified_name); ASSERT_FALSE(has_class); } TEST(test_torch_library, TestClassRegistryPrintAllClasses) { const auto& class_registry = torch::ClassRegistry::instance(); class_registry.print_all_classes(); } TEST(test_torch_library, TestOperatorRegistryHasNonExistentOperator) { auto qualified_name = "example_library::non_existent_op"; const auto& operator_registry = torch::OperatorRegistry::instance(); bool has_operator = operator_registry.has_operator(qualified_name); ASSERT_FALSE(has_operator); } TEST(test_torch_library, TestOperatorRegistryPrintAllOperators) { const auto& operator_registry = torch::OperatorRegistry::instance(); operator_registry.print_all_operators(); } TEST(test_torch_library, TestOperatorRegistryLateSchemaBindsExistingImpl) { auto& operator_registry = torch::OperatorRegistry::instance(); const std::string qualified_name = "example_library_registry_branch::late_schema_bind"; operator_registry.register_implementation( qualified_name, torch::DispatchKey::CPU, torch::CppFunction([](const torch::FunctionArgs& args) -> torch::IValue { return torch::IValue(args.get(0) + args.get(1)); })); auto* op = operator_registry.find_operator(qualified_name); ASSERT_NE(op, nullptr); auto impl_it = op->implementations.find(torch::DispatchKey::CPU); ASSERT_NE(impl_it, op->implementations.end()); torch::FunctionArgs one_arg; one_arg.add_arg(torch::IValue(int64_t(5))); EXPECT_ANY_THROW((void)impl_it->second.call_with_args(one_arg)); operator_registry.register_schema(qualified_name, "late_schema_bind(int x, int y=3) -> int"); auto bound_result = impl_it->second.call_with_args(one_arg); ASSERT_TRUE(bound_result.get_value().is_int()); EXPECT_EQ(bound_result.get_value().to_int(), 8); } TEST(test_torch_library, TestLibraryPrintInfo) { torch::Library lib("example_library_test_print_info"); lib.print_info(); } TEST(test_torch_library, TestIValueNone) { torch::IValue ival = torch::IValue(); ASSERT_TRUE(ival.is_none()); ASSERT_EQ(ival.to_repr(), "None"); ASSERT_EQ(ival.type_string(), "None"); } TEST(test_torch_library, TestIValueBool) { torch::IValue ival = true; ASSERT_TRUE(ival.is_bool()); ASSERT_EQ(ival.to_repr(), "true"); ASSERT_EQ(ival.type_string(), "Bool"); } TEST(test_torch_library, TestIValueInt) { torch::IValue ival = 42; ASSERT_TRUE(ival.is_int()); ASSERT_EQ(ival.to_repr(), "42"); ASSERT_EQ(ival.type_string(), "Int"); } TEST(test_torch_library, TestIValueDouble) { torch::IValue ival = 3.14; ASSERT_TRUE(ival.is_double()); ASSERT_TRUE(ival.to_repr().find("3.14") != std::string::npos); ASSERT_EQ(ival.type_string(), "Double"); } TEST(test_torch_library, TestIValueString) { torch::IValue ival = std::string("hello"); ASSERT_TRUE(ival.is_string()); ASSERT_EQ(ival.to_repr(), "\"hello\""); ASSERT_EQ(ival.type_string(), "String"); } TEST(test_torch_library, TestIValueTensor) { at::Tensor tensor = at::ones({2, 2}, at::kFloat); torch::IValue ival = tensor; ASSERT_TRUE(ival.is_tensor()); ASSERT_EQ(ival.type_string(), "Tensor"); } TEST(test_torch_library, TestIValueList) { std::vector vec = {1, 2, 3}; torch::IValue ival = torch::IValue(vec); ASSERT_TRUE(ival.is_list()); ASSERT_EQ(ival.to_repr(), "[1, 2, 3]"); ASSERT_EQ(ival.type_string(), "List"); } TEST(test_torch_library, TestIValueTuple) { torch::IValue ival = torch::IValue(std::make_tuple(1, true, "three")); ASSERT_TRUE(ival.is_tuple()); ASSERT_EQ(ival.to_repr(), "(1, true, \"three\")"); ASSERT_EQ(ival.type_string(), "Tuple"); }