| /* |
| * 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/any.h> |
| #include <tvm/ffi/container/array.h> |
| #include <tvm/ffi/container/variant.h> |
| #include <tvm/ffi/dtype.h> |
| #include <tvm/ffi/error.h> |
| #include <tvm/ffi/expected.h> |
| #include <tvm/ffi/function.h> |
| #include <tvm/ffi/memory.h> |
| #include <tvm/ffi/optional.h> |
| #include <tvm/ffi/reflection/registry.h> |
| |
| #include "./testing_object.h" |
| |
| namespace { |
| |
| using namespace tvm::ffi; |
| using namespace tvm::ffi::testing; |
| |
| // Test implicit construction from success value |
| TEST(Expected, BasicOk) { |
| Expected<int> result = 42; |
| |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_FALSE(result.is_err()); |
| EXPECT_TRUE(result.has_value()); |
| EXPECT_EQ(result.value(), 42); |
| EXPECT_EQ(result.value_or(0), 42); |
| } |
| |
| // Test implicit construction from error |
| TEST(Expected, BasicErr) { |
| Expected<int> result = Error("RuntimeError", "test error", ""); |
| |
| EXPECT_FALSE(result.is_ok()); |
| EXPECT_TRUE(result.is_err()); |
| EXPECT_FALSE(result.has_value()); |
| |
| Error err = result.error(); |
| EXPECT_EQ(err.kind(), "RuntimeError"); |
| EXPECT_EQ(err.message(), "test error"); |
| } |
| |
| // Test explicit construction via Unexpected wrapper |
| TEST(Expected, UnexpectedWrapper) { |
| Expected<int> result = Unexpected(Error("RuntimeError", "unexpected error", "")); |
| |
| EXPECT_FALSE(result.is_ok()); |
| EXPECT_TRUE(result.is_err()); |
| EXPECT_EQ(result.error().kind(), "RuntimeError"); |
| EXPECT_EQ(result.error().message(), "unexpected error"); |
| } |
| |
| // Test Unexpected deduction guide and error() accessor |
| TEST(Expected, UnexpectedAPI) { |
| auto u = Unexpected(Error("TypeError", "type mismatch", "")); |
| static_assert(std::is_same_v<decltype(u), Unexpected<Error>>); |
| |
| EXPECT_EQ(u.error().kind(), "TypeError"); |
| EXPECT_EQ(u.error().message(), "type mismatch"); |
| |
| Error moved_err = std::move(u).error(); |
| EXPECT_EQ(moved_err.kind(), "TypeError"); |
| } |
| |
| // Test value_or with error |
| TEST(Expected, ValueOrWithError) { |
| Expected<int> result = Error("RuntimeError", "test error", ""); |
| EXPECT_EQ(result.value_or(99), 99); |
| } |
| |
| // Test with ObjectRef types |
| TEST(Expected, ObjectRefType) { |
| Expected<TInt> result = TInt(123); |
| |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(result.value()->value, 123); |
| } |
| |
| // Test with String type |
| TEST(Expected, StringType) { |
| Expected<String> result = String("hello"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(result.value(), "hello"); |
| |
| Expected<String> err_result = Error("ValueError", "bad string", ""); |
| EXPECT_TRUE(err_result.is_err()); |
| } |
| |
| // Test TypeTraits conversion: Expected -> Any -> Expected |
| TEST(Expected, TypeTraitsRoundtrip) { |
| Expected<int> original = 42; |
| |
| // Convert to Any (should unwrap to int) |
| Any any_value = original; |
| EXPECT_EQ(any_value.cast<int>(), 42); |
| |
| // Convert back to Expected (should reconstruct as Ok) |
| Expected<int> recovered = any_value.cast<Expected<int>>(); |
| EXPECT_TRUE(recovered.is_ok()); |
| EXPECT_EQ(recovered.value(), 42); |
| } |
| |
| // Test TypeTraits conversion with Error |
| TEST(Expected, TypeTraitsErrorRoundtrip) { |
| Expected<int> original = Error("TypeError", "conversion failed", ""); |
| |
| // Convert to Any (should unwrap to Error) |
| Any any_value = original; |
| EXPECT_TRUE(any_value.as<Error>().has_value()); |
| |
| // Convert back to Expected (should reconstruct as Err) |
| Expected<int> recovered = any_value.cast<Expected<int>>(); |
| EXPECT_TRUE(recovered.is_err()); |
| EXPECT_EQ(recovered.error().kind(), "TypeError"); |
| } |
| |
| // Test move semantics |
| TEST(Expected, MoveSemantics) { |
| Expected<String> result = String("test"); |
| EXPECT_TRUE(result.is_ok()); |
| |
| String value = std::move(result).value(); |
| EXPECT_EQ(value, "test"); |
| } |
| |
| // Test CallExpected with normal function |
| TEST(Expected, CallExpectedNormal) { |
| auto safe_add = [](int a, int b) { return a + b; }; |
| |
| Function func = Function::FromTyped(safe_add); |
| Expected<int> result = func.CallExpected<int>(5, 3); |
| |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(result.value(), 8); |
| } |
| |
| // Test CallExpected with throwing function |
| TEST(Expected, CallExpectedThrowing) { |
| auto throwing_func = [](int a) -> int { |
| if (a < 0) { |
| TVM_FFI_THROW(ValueError) << "Negative value not allowed"; |
| } |
| return a * 2; |
| }; |
| |
| Function func = Function::FromTyped(throwing_func); |
| |
| // Normal case |
| Expected<int> result_ok = func.CallExpected<int>(5); |
| EXPECT_TRUE(result_ok.is_ok()); |
| EXPECT_EQ(result_ok.value(), 10); |
| |
| // Error case |
| Expected<int> result_err = func.CallExpected<int>(-1); |
| EXPECT_TRUE(result_err.is_err()); |
| EXPECT_EQ(result_err.error().kind(), "ValueError"); |
| } |
| |
| // Test that lambda returning Expected works directly |
| TEST(Expected, LambdaDirectCall) { |
| auto safe_divide = [](int a, int b) -> Expected<int> { |
| if (b == 0) { |
| return Error("ValueError", "Division by zero", ""); |
| } |
| return a / b; |
| }; |
| |
| // Direct call to lambda should work |
| Expected<int> result = safe_divide(10, 2); |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(result.value(), 5); |
| |
| // Check the value can be extracted |
| int val = result.value(); |
| EXPECT_EQ(val, 5); |
| |
| // Check assigning to Any works |
| Any any_val = result.value(); |
| EXPECT_EQ(any_val.cast<int>(), 5); |
| } |
| |
| // Test registering function that returns Expected |
| TEST(Expected, RegisterExpectedReturning) { |
| auto safe_divide = [](int a, int b) -> Expected<int> { |
| if (b == 0) { |
| return Error("ValueError", "Division by zero", ""); |
| } |
| return a / b; |
| }; |
| |
| // Verify the FunctionInfo extracts Expected<int> as return type |
| using FuncInfo = tvm::ffi::details::FunctionInfo<decltype(safe_divide)>; |
| static_assert(std::is_same_v<FuncInfo::RetType, Expected<int>>, |
| "Return type should be Expected<int>"); |
| |
| Function::SetGlobal("test.safe_divide3", Function::FromTyped(safe_divide)); |
| |
| Function func = Function::GetGlobalRequired("test.safe_divide3"); |
| |
| // Normal call should throw when function returns Err |
| EXPECT_THROW({ func(10, 0).cast<int>(); }, Error); |
| |
| // Normal call should succeed when function returns Ok |
| int result = func(10, 2).cast<int>(); |
| EXPECT_EQ(result, 5); |
| |
| // CallExpected should return Expected |
| Expected<int> exp_ok = func.CallExpected<int>(10, 2); |
| EXPECT_TRUE(exp_ok.is_ok()); |
| EXPECT_EQ(exp_ok.value(), 5); |
| |
| Expected<int> exp_err = func.CallExpected<int>(10, 0); |
| EXPECT_TRUE(exp_err.is_err()); |
| EXPECT_EQ(exp_err.error().message(), "Division by zero"); |
| } |
| |
| // Test Expected with Optional (nested types) |
| TEST(Expected, NestedOptional) { |
| Expected<Optional<int>> result = Optional<int>(42); |
| |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_TRUE(result.value().has_value()); |
| EXPECT_EQ(result.value().value(), 42); |
| |
| Expected<Optional<int>> result_none = Optional<int>(std::nullopt); |
| EXPECT_TRUE(result_none.is_ok()); |
| EXPECT_FALSE(result_none.value().has_value()); |
| } |
| |
| // Test Expected with Array |
| TEST(Expected, ArrayType) { |
| Array<int> arr{1, 2, 3}; |
| Expected<Array<int>> result = arr; |
| |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(result.value().size(), 3); |
| EXPECT_EQ(result.value()[0], 1); |
| } |
| |
| // Test complex example: function returning Expected<Array<String>> |
| TEST(Expected, ComplexExample) { |
| auto parse_csv = [](const String& input) -> Expected<Array<String>> { |
| if (input.size() == 0) { |
| return Error("ValueError", "Empty input", ""); |
| } |
| // Simple split by comma |
| Array<String> result; |
| result.push_back(input); // Simplified for test |
| return result; |
| }; |
| |
| Function::SetGlobal("test.parse_csv", Function::FromTyped(parse_csv)); |
| Function func = Function::GetGlobalRequired("test.parse_csv"); |
| |
| Expected<Array<String>> result_ok = func.CallExpected<Array<String>>(String("a,b,c")); |
| EXPECT_TRUE(result_ok.is_ok()); |
| |
| Expected<Array<String>> result_err = func.CallExpected<Array<String>>(String("")); |
| EXPECT_TRUE(result_err.is_err()); |
| EXPECT_EQ(result_err.error().message(), "Empty input"); |
| } |
| |
| // Test bad access throws the original error |
| TEST(Expected, BadAccessThrowsOriginalError) { |
| Expected<int> result = Error("CustomError", "original message", ""); |
| try { |
| result.value(); |
| FAIL() << "Expected Error to be thrown"; |
| } catch (const Error& e) { |
| // Verify the original error is preserved |
| EXPECT_EQ(e.kind(), "CustomError"); |
| EXPECT_EQ(e.message(), "original message"); |
| } |
| } |
| |
| // Test error() throws RuntimeError on bad access |
| TEST(Expected, ErrorBadAccessThrows) { |
| Expected<int> result = 42; |
| EXPECT_THROW({ result.error(); }, Error); |
| } |
| |
| // Test rvalue overload for value() |
| TEST(Expected, RvalueValueAccess) { |
| auto get_expected = []() -> Expected<String> { return String("rvalue test"); }; |
| |
| // Call value() on rvalue |
| String val = get_expected().value(); |
| EXPECT_EQ(val, "rvalue test"); |
| } |
| |
| // Test rvalue overload for error() |
| TEST(Expected, RvalueErrorAccess) { |
| auto get_expected = []() -> Expected<int> { return Error("TestError", "rvalue error", ""); }; |
| |
| // Call error() on rvalue |
| Error err = get_expected().error(); |
| EXPECT_EQ(err.kind(), "TestError"); |
| EXPECT_EQ(err.message(), "rvalue error"); |
| } |
| |
| // Test Expected<ObjectRef> with inheritance (Error is a subclass of ObjectRef) |
| // This ensures is_ok() and is_err() work correctly for ObjectRef types |
| TEST(Expected, ObjectRefInheritance) { |
| // Expected<ObjectRef> with an actual ObjectRef value |
| ObjectRef obj = TInt(100); |
| Expected<ObjectRef> result_ok(obj); |
| |
| EXPECT_TRUE(result_ok.is_ok()); |
| EXPECT_FALSE(result_ok.is_err()); |
| EXPECT_TRUE(result_ok.value().defined()); |
| |
| // Expected<ObjectRef> with an error |
| Expected<ObjectRef> result_err = Error("TestError", "test", ""); |
| |
| EXPECT_FALSE(result_err.is_ok()); |
| EXPECT_TRUE(result_err.is_err()); |
| EXPECT_EQ(result_err.error().kind(), "TestError"); |
| } |
| |
| // Test TryCastFromAnyView with incompatible type |
| TEST(Expected, TryCastIncompatible) { |
| Any any_str = String("hello"); |
| auto result = any_str.try_cast<Expected<int>>(); |
| EXPECT_FALSE(result.has_value()); // Cannot convert String to Expected<int> |
| } |
| |
| TEST(Expected, ExpectedUnsafeGetDataCompatibleStorageType) { |
| Expected<Variant<String, bool>> result = Variant<String, bool>(false); |
| |
| EXPECT_EQ(result.type_index(), TypeIndex::kTVMFFIBool); |
| EXPECT_FALSE(details::AnyUnsafe::CopyFromAnyViewAfterCheck<bool>( |
| details::ExpectedUnsafe::GetData(result))); |
| |
| Expected<Variant<String, bool>> true_result = Variant<String, bool>(true); |
| EXPECT_TRUE(details::AnyUnsafe::CopyFromAnyViewAfterCheck<bool>( |
| details::ExpectedUnsafe::GetData(true_result))); |
| } |
| |
| TEST(Expected, ExpectedUnsafeMoveBetweenExpectedStorageTypes) { |
| Expected<String> src = String("hello"); |
| TVMFFIAny raw = details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(src)); |
| Expected<Optional<String>> dst = |
| details::ExpectedUnsafe::MoveFromTVMFFIAny<Optional<String>>(raw); |
| |
| ASSERT_TRUE(dst.is_ok()); |
| ASSERT_TRUE(dst.value().has_value()); |
| EXPECT_EQ(dst.value().value(), "hello"); |
| } |
| |
| // Test that Expected<DLDataType>::value() && compiles and runs correctly. |
| // Requires TypeTraits<DLDataType>::MoveFromAnyAfterCheck to be defined. |
| TEST(ExpectedRvalueMove, DLDataTypeMoveCompiles) { |
| Expected<DLDataType> e = DLDataType{kDLFloat, 32, 1}; |
| DLDataType moved = std::move(e).value(); |
| EXPECT_EQ(moved.code, kDLFloat); |
| EXPECT_EQ(moved.bits, 32); |
| EXPECT_EQ(moved.lanes, 1); |
| } |
| |
| // Test that value_or() && moves rather than copies for Object types. |
| TEST(ExpectedRvalueMove, ValueOrMovesNotCopies) { |
| Expected<String> e = String("hello"); |
| String moved = std::move(e).value_or(String("default")); |
| EXPECT_EQ(moved, "hello"); |
| } |
| |
| // Test value_or() && on error path returns default. |
| TEST(ExpectedRvalueMove, ValueOrRvalueErrorPath) { |
| Expected<String> e = Error("ValueError", "oops", ""); |
| String result = std::move(e).value_or(String("fallback")); |
| EXPECT_EQ(result, "fallback"); |
| } |
| |
| // Test POD types compile and run correctly with rvalue value(). |
| TEST(ExpectedRvalueMove, PodTypesCompile) { |
| EXPECT_EQ(std::move(Expected<int64_t>(42)).value(), 42); |
| EXPECT_EQ(std::move(Expected<double>(3.14)).value(), 3.14); |
| EXPECT_EQ(std::move(Expected<bool>(true)).value(), true); |
| DLDataType dtype{kDLInt, 64, 1}; |
| EXPECT_EQ(std::move(Expected<DLDataType>(dtype)).value().code, kDLInt); |
| EXPECT_EQ(std::move(Expected<DLDataType>(dtype)).value().bits, 64); |
| } |
| |
| } // namespace |