blob: a1cdfb144a1b4cdf43a7389bff0a9153b44da794 [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/any.h>
#include <tvm/ffi/container/array.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 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