File
Blob: src/rust/cxx-integration-test/cxx-rust-integration-test.c++
| 1 | #include <workerd/rust/cxx-integration-test/lib.rs.h> |
| 2 | #include <workerd/rust/cxx-integration/lib.rs.h> |
| 3 | |
| 4 | #include <kj-rs/kj-rs.h> |
| 5 | #include <rust/cxx.h> |
| 6 | #include <signal.h> |
| 7 | |
| 8 | #include <kj/async.h> |
| 9 | #include <kj/test.h> |
| 10 | |
| 11 | using namespace kj_rs; |
| 12 | |
| 13 | // Test generic rust/c++ integration boundary. |
| 14 | // See src/rust/cxx-integration-tests for rust backend. |
| 15 | |
| 16 | namespace workerd::rust { |
| 17 | |
| 18 | KJ_TEST("init cxx_integration") { |
| 19 | // this tests initializes cxx integration for the rest of the tests |
| 20 | rust::cxx_integration::init(); |
| 21 | } |
| 22 | |
| 23 | KJ_TEST("panic results in abort") { |
| 24 | KJ_EXPECT_SIGNAL(SIGABRT, rust::cxx_integration::trigger_panic("foobar")); |
| 25 | } |
| 26 | |
| 27 | KJ_TEST("ok Result") { |
| 28 | KJ_EXPECT(42 == rust::test::result_ok()); |
| 29 | } |
| 30 | |
| 31 | KJ_TEST("err Result") { |
| 32 | // if fn returns an error, it is translated into kj::Exception exception. |
| 33 | try { |
| 34 | rust::test::result_error(); |
| 35 | KJ_FAIL_REQUIRE("exception is expected"); |
| 36 | } catch (kj::Exception& e) { |
| 37 | // this is expected |
| 38 | KJ_EXPECT(e.getDescription() == "test error"_kj); |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | KJ_TEST("err Result with getCaughtExceptionAsKj") { |
| 43 | // if fn returns an error, it is translated into kj::Exception exception that can be accessed |
| 44 | // using getCaughtExceptionAsKj as is very common in the source code. |
| 45 | try { |
| 46 | rust::test::result_error(); |
| 47 | KJ_FAIL_REQUIRE("exception is expected"); |
| 48 | } catch (...) { |
| 49 | // this is expected |
| 50 | KJ_EXPECT(kj::getCaughtExceptionAsKj().getDescription() == "test error"_kj); |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | KJ_TEST("test callback") { |
| 55 | rust::test::TestCallback callback = [](size_t a, size_t b) { return a + b; }; |
| 56 | auto result = rust::test::call_callback(callback, 40, 2); |
| 57 | KJ_EXPECT(result == 42); |
| 58 | } |
| 59 | |
| 60 | KJ_TEST("test crashing callback") { |
| 61 | rust::test::TestCallback callback = [](size_t a, size_t b) -> size_t { |
| 62 | KJ_FAIL_REQUIRE("expected to crash"); |
| 63 | }; |
| 64 | // std::terminate is called when c++ throws fatal except |
| 65 | KJ_EXPECT_SIGNAL(SIGABRT, rust::test::call_callback(callback, 40, 2)); |
| 66 | } |
| 67 | |
| 68 | KJ_TEST("test recoverable exception callback") { |
| 69 | rust::test::TestCallback callback = [](size_t a, size_t b) -> size_t { |
| 70 | kj::throwRecoverableException(KJ_EXCEPTION(DISCONNECTED, "Premature EOF.")); |
| 71 | KJ_UNREACHABLE; |
| 72 | }; |
| 73 | // std::terminate is called when c++ throws unhandled recoverable exception |
| 74 | KJ_EXPECT_SIGNAL(SIGABRT, rust::test::call_callback(callback, 40, 2)); |
| 75 | } |
| 76 | |
| 77 | KJ_TEST("shared structure") { |
| 78 | { |
| 79 | // rust structure arguments are passed by-value in c++ |
| 80 | const auto s = rust::test::SharedStruct{.a = 20, .b = 22}; |
| 81 | KJ_EXPECT(42 == rust::test::pass_shared_struct(s)); |
| 82 | } |
| 83 | |
| 84 | { |
| 85 | // rust structure return values are return by-value in c++ |
| 86 | const auto s = rust::test::return_shared_struct(); |
| 87 | KJ_EXPECT(13 == s.a); |
| 88 | KJ_EXPECT(29 == s.b); |
| 89 | } |
| 90 | |
| 91 | { |
| 92 | // rust reference looks like const reference to c++; |
| 93 | const auto s = rust::test::SharedStruct{.a = 20, .b = 22}; |
| 94 | KJ_EXPECT(42 == rust::test::pass_shared_struct_as_ref(s)); |
| 95 | } |
| 96 | |
| 97 | { |
| 98 | // rust mutable reference looks like reference to c++ |
| 99 | auto s = rust::test::SharedStruct{.a = 10, .b = 32}; |
| 100 | rust::test::pass_shared_struct_as_mut_ref(s); |
| 101 | KJ_EXPECT(s.a == 42); |
| 102 | KJ_EXPECT(s.b == 0); |
| 103 | } |
| 104 | |
| 105 | { |
| 106 | // rust const pointer looks like const pointer to c++ |
| 107 | const auto s = rust::test::SharedStruct{.a = 20, .b = 22}; |
| 108 | KJ_EXPECT(42 == rust::test::pass_shared_struct_as_const_ptr(&s)); |
| 109 | } |
| 110 | |
| 111 | { |
| 112 | // rust mut pointer looks like pointer to c++ |
| 113 | auto s = rust::test::SharedStruct{.a = 10, .b = 32}; |
| 114 | rust::test::pass_shared_struct_as_mut_ptr(&s); |
| 115 | KJ_EXPECT(s.a == 0); |
| 116 | KJ_EXPECT(s.b == 0); |
| 117 | } |
| 118 | |
| 119 | { |
| 120 | // rust Box<T> type is represented as special ::rust::Box<T> c++ type |
| 121 | // there are many ways to create a Box |
| 122 | |
| 123 | { |
| 124 | // box can be created by copying the value |
| 125 | auto box = ::rust::Box<rust::test::SharedStruct>(rust::test::SharedStruct{.a = 3, .b = 39}); |
| 126 | // box is consumed by the call as expected |
| 127 | KJ_EXPECT(42 == rust::test::pass_shared_struct_as_box(kj::mv(box))); |
| 128 | } |
| 129 | |
| 130 | { |
| 131 | // box can be created by moving the value |
| 132 | auto s = rust::test::SharedStruct{.a = 3, .b = 39}; |
| 133 | auto box = ::rust::Box<rust::test::SharedStruct>(kj::mv(s)); |
| 134 | KJ_EXPECT(42 == rust::test::pass_shared_struct_as_box(kj::mv(box))); |
| 135 | } |
| 136 | |
| 137 | { |
| 138 | // box can be created from raw pointer. |
| 139 | // Memory needs to be allocated using malloc, since rust doesn't have |
| 140 | // access to delete. |
| 141 | auto mem = malloc(sizeof(rust::test::SharedStruct)); |
| 142 | auto s = new (mem) rust::test::SharedStruct; |
| 143 | s->a = 4; |
| 144 | s->b = 38; |
| 145 | auto box = ::rust::Box<rust::test::SharedStruct>::from_raw(s); |
| 146 | KJ_EXPECT(42 == rust::test::pass_shared_struct_as_box(kj::mv(box))); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | { |
| 151 | // box can be returned from rust to c++ as well |
| 152 | auto box = rust::test::return_shared_struct_as_box(); |
| 153 | KJ_EXPECT(1 == box->a); |
| 154 | KJ_EXPECT(41 == box->b); |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | KJ_TEST("opaque rust type") { |
| 159 | // &str is represented as a special ::rust::Str type |
| 160 | // it supports variety of implicit constructors. |
| 161 | auto s = rust::test::rust_struct_new_box("test_name"); |
| 162 | ::rust::Str name = s->get_name(); |
| 163 | |
| 164 | // ::rust::Str is _not_ null-terminated so kj::StringPtr can't be created from |
| 165 | // it. need to allocate to create c++-string (or use it as ArrayPtr). |
| 166 | auto strName = std::string(name); |
| 167 | KJ_EXPECT("test_name"_kj == kj::StringPtr(strName.c_str())); |
| 168 | |
| 169 | s->set_name("another_name"); |
| 170 | KJ_EXPECT("another_name"_kjc == kj::arrayPtr(s->get_name().data(), s->get_name().size())); |
| 171 | } |
| 172 | |
| 173 | KJ_TEST("rust::String test") { |
| 174 | auto s = rust::test::get_string(); |
| 175 | auto expected = "rust_string"_kj; |
| 176 | KJ_EXPECT(expected == kj::str(s)); |
| 177 | KJ_EXPECT(expected == kj::toCharSequence(s)); |
| 178 | KJ_EXPECT(kj::hashCode(expected) == kj::hashCode(s)); |
| 179 | } |
| 180 | |
| 181 | KJ_TEST("rust::str test") { |
| 182 | auto s = rust::test::get_str(); |
| 183 | auto expected = "rust_str"_kj; |
| 184 | KJ_EXPECT(expected == kj::str(s)); |
| 185 | KJ_EXPECT(expected == kj::toCharSequence(s)); |
| 186 | KJ_EXPECT(kj::hashCode(expected) == kj::hashCode(s)); |
| 187 | } |
| 188 | |
| 189 | KJ_TEST("test async immediate future") { |
| 190 | kj::EventLoop loop; |
| 191 | kj::WaitScope waitScope(loop); |
| 192 | |
| 193 | auto pair = kj::newPromiseAndCrossThreadFulfiller<size_t>(); |
| 194 | |
| 195 | rust::test::UsizeCallback callback = [&](size_t a) { pair.fulfiller->fulfill(kj::mv(a)); }; |
| 196 | rust::test::async_immediate(callback); |
| 197 | |
| 198 | auto result = pair.promise.wait(waitScope); |
| 199 | KJ_EXPECT(result == 42); |
| 200 | } |
| 201 | |
| 202 | KJ_TEST("test async delay") { |
| 203 | kj::EventLoop loop; |
| 204 | kj::WaitScope waitScope(loop); |
| 205 | |
| 206 | auto pair = kj::newPromiseAndCrossThreadFulfiller<size_t>(); |
| 207 | |
| 208 | rust::test::UsizeCallback callback = [&](size_t a) { pair.fulfiller->fulfill(kj::mv(a)); }; |
| 209 | rust::test::async_sleep(callback); |
| 210 | |
| 211 | auto result = pair.promise.wait(waitScope); |
| 212 | KJ_EXPECT(result == 42); |
| 213 | } |
| 214 | |
| 215 | KJ_TEST("array/slice convertions") { |
| 216 | // const arrayPtr -> const slice |
| 217 | { |
| 218 | kj::ArrayPtr<const kj::byte> a = "foo"_kjb; |
| 219 | ::rust::Slice<const kj::byte> s = a.as<Rust>(); |
| 220 | KJ_EXPECT(s.length() == a.size()); |
| 221 | } |
| 222 | |
| 223 | // mutable arrayPtr -> const slice |
| 224 | { |
| 225 | kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20); |
| 226 | kj::ArrayPtr<kj::byte> p = a.asPtr(); |
| 227 | ::rust::Slice<const kj::byte> s = p.as<Rust>(); |
| 228 | KJ_EXPECT(s.length() == a.size()); |
| 229 | } |
| 230 | |
| 231 | // mutable arrayPtr -> mutable slice |
| 232 | { |
| 233 | kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20); |
| 234 | ::rust::Slice<kj::byte> s = a.asPtr().as<RustMutable>(); |
| 235 | KJ_EXPECT(s.length() == a.size()); |
| 236 | } |
| 237 | |
| 238 | // const array -> const slice |
| 239 | { |
| 240 | kj::Array<const kj::byte> a = kj::heapArray<kj::byte>(20); |
| 241 | ::rust::Slice<const kj::byte> s = a.as<Rust>(); |
| 242 | KJ_EXPECT(s.length() == a.size()); |
| 243 | } |
| 244 | |
| 245 | // mutable array -> const slice |
| 246 | { |
| 247 | kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20); |
| 248 | ::rust::Slice<const kj::byte> s = a.as<Rust>(); |
| 249 | KJ_EXPECT(s.length() == a.size()); |
| 250 | } |
| 251 | |
| 252 | // mutable array -> mutable slice |
| 253 | { |
| 254 | kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20); |
| 255 | ::rust::Slice<kj::byte> s = a.as<RustMutable>(); |
| 256 | KJ_EXPECT(s.length() == a.size()); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | } // namespace workerd::rust |