1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <atomic>
18 #include <cstdint>
19 #include <thread>
20
21 #include <folly/Memory.h>
22 #include <folly/io/async/EventBase.h>
23 #include <folly/io/async/Request.h>
24 #include <folly/io/async/test/RequestContextHelper.h>
25 #include <folly/portability/GTest.h>
26 #include <folly/system/ThreadName.h>
27
28 #include <boost/thread/barrier.hpp>
29
30 using namespace folly;
31
32 RequestToken testtoken("test");
33
34 class RequestContextTest : public ::testing::Test {
35 protected:
SetUp()36 void SetUp() override {
37 // Make sure each test starts out using the default context, and not some
38 // other context left over by a previous test.
39 RequestContext::setContext(nullptr);
40
41 // Make sure no data is set for the "test" key when we start. There could
42 // be left over data in the default context from a previous test. If we
43 // don't clear it out future calls to setContextData() won't actually work,
44 // and will reset the data to null instead of properly setting the new
45 // desired data.
46 //
47 // (All of the tests generally want the behavior of overwriteContextData()
48 // rather than setContextData(), but that method is private.)
49 //
50 // We ideally want to clear out data for any keys that may be set, not just
51 // the "test" key, but there also isn't a RequestContext API to do this.
52 clearData();
53 }
54
getContext()55 RequestContext& getContext() {
56 auto* ctx = RequestContext::get();
57 EXPECT_TRUE(ctx != nullptr);
58 return *ctx;
59 }
60
setData(int data=0,std::string key="test")61 void setData(int data = 0, std::string key = "test") {
62 getContext().setContextData(key, std::make_unique<TestData>(data));
63 }
64
hasData(std::string key="test")65 bool hasData(std::string key = "test") {
66 return getContext().hasContextData(key);
67 }
68
getData(std::string key="test")69 const TestData& getData(std::string key = "test") {
70 auto* ptr = dynamic_cast<TestData*>(getContext().getContextData(key));
71 EXPECT_TRUE(ptr != nullptr);
72 return *ptr;
73 }
74
clearData(std::string key="test")75 void clearData(std::string key = "test") {
76 getContext().clearContextData(key);
77 }
78
getRootIdsFromAllThreads()79 std::vector<intptr_t> getRootIdsFromAllThreads() {
80 auto rootids = RequestContext::getRootIdsFromAllThreads();
81 std::vector<intptr_t> result;
82 std::transform(
83 rootids.begin(), rootids.end(), std::back_inserter(result), [](auto e) {
84 return e.id;
85 });
86 return result;
87 }
88 };
89
TEST_F(RequestContextTest,SimpleTest)90 TEST_F(RequestContextTest, SimpleTest) {
91 EventBase base;
92
93 // There should always be a default context with get()
94 EXPECT_TRUE(RequestContext::get() != nullptr);
95 // but fallback context should have rootid set to 0
96 EXPECT_EQ(RequestContext::get()->getRootId(), 0);
97
98 // but not with saveContext()
99 EXPECT_EQ(RequestContext::saveContext(), nullptr);
100 RequestContext::create();
101 EXPECT_NE(RequestContext::saveContext(), nullptr);
102 auto rootids = getRootIdsFromAllThreads();
103 EXPECT_EQ(1, rootids.size());
104 EXPECT_EQ(RequestContext::get()->getRootId(), rootids[0]);
105 EXPECT_EQ(reinterpret_cast<intptr_t>(RequestContext::get()), rootids[0]);
106 EXPECT_NE(RequestContext::get()->getRootId(), 0);
107 RequestContext::create();
108 EXPECT_NE(RequestContext::saveContext(), nullptr);
109 EXPECT_NE(RequestContext::get()->getRootId(), rootids[0]);
110
111 EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test"));
112
113 RequestContext::get()->setContextData("test", std::make_unique<TestData>(10));
114 base.runInEventBaseThread([&]() {
115 EXPECT_TRUE(RequestContext::get() != nullptr);
116 auto data = dynamic_cast<TestData*>(
117 RequestContext::get()->getContextData(testtoken))
118 ->data_;
119 EXPECT_EQ(10, data);
120 rootids = getRootIdsFromAllThreads();
121 EXPECT_EQ(2, rootids.size());
122 EXPECT_EQ(RequestContext::get()->getRootId(), rootids[0]);
123 EXPECT_EQ(RequestContext::get()->getRootId(), rootids[1]);
124 base.terminateLoopSoon();
125 });
126 auto th = std::thread([&]() { base.loopForever(); });
127 th.join();
128 EXPECT_TRUE(RequestContext::get() != nullptr);
129 auto a =
130 dynamic_cast<TestData*>(RequestContext::get()->getContextData("test"));
131 auto data = a->data_;
132 EXPECT_EQ(10, data);
133
134 RequestContext::setContext(std::shared_ptr<RequestContext>());
135 // There should always be a default context
136 EXPECT_TRUE(nullptr != RequestContext::get());
137 }
138
TEST_F(RequestContextTest,RequestContextScopeGuard)139 TEST_F(RequestContextTest, RequestContextScopeGuard) {
140 RequestContextScopeGuard g0;
141 setData(10);
142 {
143 RequestContextScopeGuard g1;
144 EXPECT_FALSE(hasData());
145 setData(20);
146 EXPECT_EQ(20, getData().data_);
147 EXPECT_EQ(1, getData().set_);
148 EXPECT_EQ(0, getData().unset_);
149 }
150 EXPECT_EQ(10, getData().data_);
151 EXPECT_EQ(2, getData().set_);
152 EXPECT_EQ(1, getData().unset_);
153 }
154
TEST_F(RequestContextTest,defaultContext)155 TEST_F(RequestContextTest, defaultContext) {
156 // Don't create a top level guard
157 setData(10);
158 {
159 RequestContextScopeGuard g1;
160 EXPECT_FALSE(hasData());
161 }
162 EXPECT_EQ(10, getData().data_);
163 EXPECT_EQ(1, getData().set_);
164 EXPECT_EQ(0, getData().unset_);
165 }
166
TEST_F(RequestContextTest,setIfAbsentTest)167 TEST_F(RequestContextTest, setIfAbsentTest) {
168 EXPECT_TRUE(RequestContext::get() != nullptr);
169
170 RequestContext::get()->setContextData("test", std::make_unique<TestData>(10));
171 EXPECT_FALSE(RequestContext::get()->setContextDataIfAbsent(
172 "test", std::make_unique<TestData>(20)));
173 EXPECT_EQ(
174 10,
175 dynamic_cast<TestData*>(RequestContext::get()->getContextData(testtoken))
176 ->data_);
177
178 EXPECT_TRUE(RequestContext::get()->setContextDataIfAbsent(
179 "test2", std::make_unique<TestData>(20)));
180 EXPECT_EQ(
181 20,
182 dynamic_cast<TestData*>(RequestContext::get()->getContextData("test2"))
183 ->data_);
184
185 RequestContext::setContext(std::shared_ptr<RequestContext>());
186 EXPECT_TRUE(nullptr != RequestContext::get());
187 }
188
TEST_F(RequestContextTest,testSetUnset)189 TEST_F(RequestContextTest, testSetUnset) {
190 RequestContext::create();
191 auto ctx1 = RequestContext::saveContext();
192 ctx1->setContextData("test", std::make_unique<TestData>(10));
193 auto testData1 = dynamic_cast<TestData*>(ctx1->getContextData("test"));
194
195 // onSet called in setContextData
196 EXPECT_EQ(1, testData1->set_);
197 EXPECT_EQ(ctx1.get(), testData1->onSetRctx);
198
199 // Override RequestContext
200 RequestContext::create();
201 auto ctx2 = RequestContext::saveContext();
202 ctx2->setContextData(testtoken, std::make_unique<TestData>(20));
203 auto testData2 = dynamic_cast<TestData*>(ctx2->getContextData(testtoken));
204
205 // onSet called in setContextData
206 EXPECT_EQ(1, testData2->set_);
207 EXPECT_EQ(ctx2.get(), testData2->onSetRctx);
208
209 // Check ctx1->onUnset was called
210 EXPECT_EQ(1, testData1->unset_);
211 EXPECT_EQ(ctx1.get(), testData1->onUnSetRctx);
212
213 RequestContext::setContext(ctx1);
214 EXPECT_EQ(2, testData1->set_);
215 EXPECT_EQ(1, testData1->unset_);
216 EXPECT_EQ(1, testData2->unset_);
217 EXPECT_EQ(ctx1.get(), testData1->onSetRctx);
218 EXPECT_EQ(ctx1.get(), testData1->onUnSetRctx);
219 EXPECT_EQ(ctx2.get(), testData2->onUnSetRctx);
220
221 RequestContext::setContext(ctx2);
222 EXPECT_EQ(2, testData1->set_);
223 EXPECT_EQ(2, testData1->unset_);
224 EXPECT_EQ(2, testData2->set_);
225 EXPECT_EQ(1, testData2->unset_);
226 }
227
TEST_F(RequestContextTest,deadlockTest)228 TEST_F(RequestContextTest, deadlockTest) {
229 class DeadlockTestData : public RequestData {
230 public:
231 explicit DeadlockTestData(const std::string& val) : val_(val) {}
232
233 ~DeadlockTestData() override {
234 RequestContext::get()->setContextData(
235 val_, std::make_unique<TestData>(1));
236 }
237
238 bool hasCallback() override { return false; }
239
240 std::string val_;
241 };
242
243 RequestContext::get()->setContextData(
244 "test", std::make_unique<DeadlockTestData>("test1"));
245 RequestContext::get()->clearContextData(testtoken);
246 }
247
248 // A common use case is to use set/unset to maintain a thread global
249 // Regression test to ensure that unset is always called before set
TEST_F(RequestContextTest,sharedGlobalTest)250 TEST_F(RequestContextTest, sharedGlobalTest) {
251 static bool global = false;
252
253 class GlobalTestData : public RequestData {
254 public:
255 void onSet() override {
256 ASSERT_FALSE(global);
257 global = true;
258 }
259
260 void onUnset() override {
261 ASSERT_TRUE(global);
262 global = false;
263 }
264
265 bool hasCallback() override { return true; }
266 };
267
268 intptr_t root = 0;
269 {
270 RequestContextScopeGuard g0;
271 RequestContext::get()->setContextData(
272 "test", std::make_unique<GlobalTestData>());
273 auto root0 = RequestContext::saveContext().get()->getRootId();
274 EXPECT_EQ(getRootIdsFromAllThreads()[0], root0);
275 {
276 RequestContextScopeGuard g1;
277 RequestContext::get()->setContextData(
278 "test", std::make_unique<GlobalTestData>());
279 auto root1 = RequestContext::saveContext().get()->getRootId();
280 EXPECT_EQ(getRootIdsFromAllThreads()[0], root1);
281 }
282 EXPECT_EQ(getRootIdsFromAllThreads()[0], root0);
283 }
284 EXPECT_EQ(getRootIdsFromAllThreads()[0], root);
285 }
286
TEST_F(RequestContextTest,ShallowCopyBasic)287 TEST_F(RequestContextTest, ShallowCopyBasic) {
288 ShallowCopyRequestContextScopeGuard g0;
289 setData(123, "immutable");
290 EXPECT_EQ(123, getData("immutable").data_);
291 EXPECT_FALSE(hasData());
292 EXPECT_EQ(0, getRootIdsFromAllThreads()[0]);
293
294 {
295 ShallowCopyRequestContextScopeGuard g1;
296 EXPECT_EQ(123, getData("immutable").data_);
297 setData(789);
298 EXPECT_EQ(789, getData().data_);
299 EXPECT_EQ(0, getRootIdsFromAllThreads()[0]);
300 }
301
302 EXPECT_FALSE(hasData());
303 EXPECT_EQ(123, getData("immutable").data_);
304 EXPECT_EQ(1, getData("immutable").set_);
305 EXPECT_EQ(0, getData("immutable").unset_);
306 EXPECT_EQ(0, getRootIdsFromAllThreads()[0]);
307 }
308
TEST_F(RequestContextTest,ShallowCopyOverwrite)309 TEST_F(RequestContextTest, ShallowCopyOverwrite) {
310 RequestContextScopeGuard g0;
311 setData(123);
312 EXPECT_EQ(123, getData().data_);
313 auto rootid = RequestContext::get()->getRootId();
314 EXPECT_EQ(rootid, getRootIdsFromAllThreads()[0]);
315 {
316 ShallowCopyRequestContextScopeGuard g1(
317 "test", std::make_unique<TestData>(789));
318 EXPECT_EQ(789, getData().data_);
319 EXPECT_EQ(1, getData().set_);
320 EXPECT_EQ(0, getData().unset_);
321 // should have inherited parent's rootid
322 EXPECT_EQ(rootid, getRootIdsFromAllThreads()[0]);
323
324 {
325 // rootId is preserved for shallow copies of shallow copies
326 ShallowCopyRequestContextScopeGuard g2;
327 EXPECT_EQ(rootid, getRootIdsFromAllThreads()[0]);
328 }
329 EXPECT_EQ(rootid, getRootIdsFromAllThreads()[0]);
330 }
331 EXPECT_EQ(123, getData().data_);
332 EXPECT_EQ(2, getData().set_);
333 EXPECT_EQ(1, getData().unset_);
334 EXPECT_EQ(rootid, getRootIdsFromAllThreads()[0]);
335 }
336
TEST_F(RequestContextTest,ShallowCopyDefaultContext)337 TEST_F(RequestContextTest, ShallowCopyDefaultContext) {
338 // Don't set global scope guard
339 setData(123);
340 EXPECT_EQ(123, getData().data_);
341 {
342 ShallowCopyRequestContextScopeGuard g1(
343 "test", std::make_unique<TestData>(789));
344 EXPECT_EQ(789, getData().data_);
345 }
346 EXPECT_EQ(123, getData().data_);
347 EXPECT_EQ(1, getData().set_);
348 EXPECT_EQ(0, getData().unset_);
349 }
350
TEST_F(RequestContextTest,ShallowCopyClear)351 TEST_F(RequestContextTest, ShallowCopyClear) {
352 RequestContextScopeGuard g0;
353 setData(123);
354 EXPECT_EQ(123, getData().data_);
355 {
356 ShallowCopyRequestContextScopeGuard g1;
357 EXPECT_EQ(123, getData().data_);
358 clearData();
359 setData(789);
360 EXPECT_EQ(789, getData().data_);
361 }
362 EXPECT_EQ(123, getData().data_);
363 EXPECT_EQ(2, getData().set_);
364 EXPECT_EQ(1, getData().unset_);
365 }
366
TEST_F(RequestContextTest,ShallowCopyMulti)367 TEST_F(RequestContextTest, ShallowCopyMulti) {
368 RequestContextScopeGuard g0;
369 setData(1, "test1");
370 setData(2, "test2");
371 EXPECT_EQ(1, getData("test1").data_);
372 EXPECT_EQ(2, getData("test2").data_);
373 {
374 ShallowCopyRequestContextScopeGuard g1(
375 RequestDataItem{"test1", std::make_unique<TestData>(2)},
376 RequestDataItem{"test2", std::make_unique<TestData>(4)});
377
378 EXPECT_EQ(2, getData("test1").data_);
379 EXPECT_EQ(4, getData("test2").data_);
380 clearData("test1");
381 clearData("test2");
382 setData(4, "test1");
383 setData(8, "test2");
384 EXPECT_EQ(4, getData("test1").data_);
385 EXPECT_EQ(8, getData("test2").data_);
386 }
387 EXPECT_EQ(1, getData("test1").data_);
388 EXPECT_EQ(2, getData("test2").data_);
389 }
390
TEST_F(RequestContextTest,RootIdOnCopy)391 TEST_F(RequestContextTest, RootIdOnCopy) {
392 auto ctxBase = std::make_shared<RequestContext>(0xab);
393 EXPECT_EQ(0xab, ctxBase->getRootId());
394 {
395 auto ctx = RequestContext::copyAsRoot(*ctxBase, 0xabc);
396 EXPECT_EQ(0xabc, ctx->getRootId());
397 }
398 {
399 auto ctx = RequestContext::copyAsChild(*ctxBase);
400 EXPECT_EQ(0xab, ctx->getRootId());
401 }
402 }
403
TEST_F(RequestContextTest,ThreadId)404 TEST_F(RequestContextTest, ThreadId) {
405 folly::setThreadName("DummyThread");
406 RequestContextScopeGuard g;
407 auto ctxBase = std::make_shared<RequestContext>();
408 auto rootids = RequestContext::getRootIdsFromAllThreads();
409 EXPECT_EQ(*folly::getThreadName(rootids[0].tid), "DummyThread");
410 EXPECT_EQ(rootids[0].tidOS, folly::getOSThreadID());
411
412 EventBase base;
413 base.runInEventBaseThread([&]() {
414 RequestContextScopeGuard g;
415 folly::setThreadName("DummyThread2");
416 rootids = RequestContext::getRootIdsFromAllThreads();
417 base.terminateLoopSoon();
418 });
419
420 auto th = std::thread([&]() { base.loopForever(); });
421 th.join();
422
423 std::sort(rootids.begin(), rootids.end(), [](const auto& a, const auto& b) {
424 auto aname = folly::getThreadName(a.tid);
425 auto bname = folly::getThreadName(b.tid);
426 return (aname ? *aname : "zzz") < (bname ? *bname : "zzz");
427 });
428
429 EXPECT_EQ(*folly::getThreadName(rootids[0].tid), "DummyThread");
430 EXPECT_FALSE(folly::getThreadName(rootids[1].tid));
431 }
432
TEST_F(RequestContextTest,Clear)433 TEST_F(RequestContextTest, Clear) {
434 struct Foo : public RequestData {
435 bool& cleared;
436 bool& deleted;
437 Foo(bool& c, bool& d) : cleared(c), deleted(d) {}
438 ~Foo() override {
439 EXPECT_TRUE(cleared);
440 deleted = true;
441 }
442 bool hasCallback() override { return false; }
443 void onClear() override {
444 EXPECT_FALSE(cleared);
445 cleared = true;
446 }
447 };
448
449 std::string key = "clear";
450 {
451 bool cleared = false;
452 bool deleted = false;
453 {
454 RequestContextScopeGuard g;
455 RequestContext::get()->setContextData(
456 key, std::make_unique<Foo>(cleared, deleted));
457 EXPECT_FALSE(cleared);
458 RequestContext::get()->clearContextData(key);
459 EXPECT_TRUE(cleared);
460 }
461 EXPECT_TRUE(deleted);
462 }
463 {
464 bool cleared = false;
465 bool deleted = false;
466 {
467 RequestContextScopeGuard g;
468 RequestContext::get()->setContextData(
469 key, std::make_unique<Foo>(cleared, deleted));
470 EXPECT_FALSE(cleared);
471 EXPECT_FALSE(deleted);
472 }
473 EXPECT_TRUE(cleared);
474 EXPECT_TRUE(deleted);
475 }
476 }
477
TEST_F(RequestContextTest,OverwriteNullData)478 TEST_F(RequestContextTest, OverwriteNullData) {
479 folly::ShallowCopyRequestContextScopeGuard g0("token", nullptr);
480 {
481 folly::ShallowCopyRequestContextScopeGuard g1(
482 "token", std::make_unique<TestData>(0));
483 EXPECT_NE(folly::RequestContext::get()->getContextData("token"), nullptr);
484 }
485 }
486
TEST_F(RequestContextTest,ConcurrentDataRefRelease)487 TEST_F(RequestContextTest, ConcurrentDataRefRelease) {
488 for (int i = 0; i < 100; ++i) {
489 std::atomic<int> step{0};
490 std::shared_ptr<folly::RequestContext> sp1;
491 auto th1 = std::thread([&]() {
492 folly::RequestContextScopeGuard g0; // Creates ctx0.
493 setData(); // Creates data0 with one reference in ctx0.
494 {
495 folly::ShallowCopyRequestContextScopeGuard g1;
496 // g1 created ctx1 with second reference to data0.
497 EXPECT_NE(&getData(), nullptr);
498 // Keep shared_ptr to ctx1 to pass to th2
499 sp1 = folly::RequestContext::saveContext();
500 step.store(1); // sp1 is ready.
501 while (step.load() < 2)
502 /* Wait for th2 to clear reference to data0. */;
503 }
504 // End of g2 released shared_ptr to ctx1, switched back to ctx0
505 // At this point:
506 // - One shared_ptr to ctx0, held by th1.
507 // - One shared_ptr to ctx1, help by th2.
508 // - data0 has one clear count (for reference from ctx0) and
509 // two delete counts (one each from ctx0 and ctx1).
510 step.store(3);
511 // End of g1 will destroy ctx0, release clear/delete counts for data0.
512 });
513 auto th2 = std::thread([&]() {
514 while (step.load() < 1)
515 /* Wait for th1 to set sp1. */;
516 folly::RequestContextScopeGuard g2(std::move(sp1));
517 // g2 set context to ctx1.
518 EXPECT_EQ(sp1.get(), nullptr);
519 EXPECT_NE(&getData(), nullptr);
520 clearData();
521 step.store(2); // th2 cleared reference to data0 in ctx1.
522 while (step.load() < 3)
523 /* Wait for th1 to release shared_ptr to ctx1. */;
524 // End of g2 will destroy ctx1, release delete count for data0.
525 });
526 th1.join();
527 th2.join();
528 }
529 }
530
TEST_F(RequestContextTest,AccessAllThreadsDestructionGuard)531 TEST_F(RequestContextTest, AccessAllThreadsDestructionGuard) {
532 constexpr auto kNumThreads = 128;
533
534 std::vector<std::thread> threads{kNumThreads};
535 boost::barrier barrier{kNumThreads + 1};
536
537 std::atomic<std::size_t> count{0};
538 for (auto& thread : threads) {
539 thread = std::thread([&] {
540 // Force creation of thread local
541 RequestContext::get();
542 ++count;
543 // Wait for all other threads to do the same
544 barrier.wait();
545 // Wait until signaled to die
546 barrier.wait();
547 });
548 }
549
550 barrier.wait();
551 // Sanity check
552 EXPECT_EQ(count.load(), kNumThreads);
553
554 {
555 auto accessor = RequestContext::accessAllThreads();
556 // Allow threads to die (but they should not as long as we hold accessor!)
557 barrier.wait();
558 auto accessorsCount = std::distance(accessor.begin(), accessor.end());
559 EXPECT_EQ(accessorsCount, kNumThreads + 1);
560 for (RequestContext::StaticContext& staticContext : accessor) {
561 EXPECT_EQ(staticContext.requestContext, nullptr);
562 }
563 }
564
565 for (auto& thread : threads) {
566 thread.join();
567 }
568 }
569
TEST(RequestContextTryGetTest,TryGetTest)570 TEST(RequestContextTryGetTest, TryGetTest) {
571 // try_get() should not create a default RequestContext object if none exists.
572 EXPECT_EQ(RequestContext::try_get(), nullptr);
573 // Explicitly create a new instance so that subsequent calls to try_get()
574 // return it.
575 RequestContext::create();
576 EXPECT_NE(RequestContext::saveContext(), nullptr);
577 EXPECT_NE(RequestContext::try_get(), nullptr);
578 // Make sure that the pointers returned by both get() and try_get() point to
579 // the same underlying instance.
580 EXPECT_EQ(RequestContext::try_get(), RequestContext::get());
581 // Set some context data and read it out via try_get() accessor.
582 RequestContext::get()->setContextData("test", std::make_unique<TestData>(10));
583 auto rc = RequestContext::try_get();
584 EXPECT_TRUE(rc->hasContextData("test"));
585 auto* dataPtr = dynamic_cast<TestData*>(rc->getContextData("test"));
586 EXPECT_EQ(dataPtr->data_, 10);
587
588 auto thread = std::thread([&] {
589 auto accessor = RequestContext::accessAllThreads();
590 // test there is no deadlock with try_get()
591 RequestContext::try_get();
592 });
593 thread.join();
594
595 thread = std::thread([&] {
596 RequestContext::get();
597 auto accessor = RequestContext::accessAllThreads();
598 // test there is no deadlock with get()
599 RequestContext::get();
600 });
601 thread.join();
602 }
603
TEST(ImmutableRequestTest,simple)604 TEST(ImmutableRequestTest, simple) {
605 ImmutableRequestData<int> ird(4);
606 EXPECT_EQ(ird.value(), 4);
607 }
608
TEST(ImmutableRequestTest,type_traits)609 TEST(ImmutableRequestTest, type_traits) {
610 using IRDI = ImmutableRequestData<int>;
611
612 auto c1 = std::is_constructible<IRDI, int>::value;
613 EXPECT_TRUE(c1);
614 auto n1 = std::is_nothrow_constructible<IRDI, int>::value;
615 EXPECT_TRUE(n1);
616
617 auto c2 = std::is_constructible<IRDI, int, int>::value;
618 EXPECT_FALSE(c2);
619 }
620