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 <thread>
18 
19 #include <folly/MPMCQueue.h>
20 #include <folly/executors/DrivableExecutor.h>
21 #include <folly/executors/InlineExecutor.h>
22 #include <folly/executors/ManualExecutor.h>
23 #include <folly/futures/Future.h>
24 #include <folly/portability/GTest.h>
25 #include <folly/synchronization/Baton.h>
26 
27 using namespace folly;
28 
29 struct ManualWaiter : public DrivableExecutor {
ManualWaiterManualWaiter30   explicit ManualWaiter(std::shared_ptr<ManualExecutor> ex_) : ex(ex_) {}
31 
addManualWaiter32   void add(Func f) override { ex->add(std::move(f)); }
33 
driveManualWaiter34   void drive() override {
35     ex->wait();
36     ex->run();
37   }
38 
39   std::shared_ptr<ManualExecutor> ex;
40 };
41 
42 struct ViaFixture : public testing::Test {
ViaFixtureViaFixture43   ViaFixture()
44       : westExecutor(new ManualExecutor),
45         eastExecutor(new ManualExecutor),
46         waiter(new ManualWaiter(westExecutor)),
47         done(false) {
48     th = std::thread([=] {
49       ManualWaiter eastWaiter(eastExecutor);
50       while (!done) {
51         eastWaiter.drive();
52       }
53     });
54   }
55 
~ViaFixtureViaFixture56   ~ViaFixture() override {
57     done = true;
58     eastExecutor->add([=]() {});
59     th.join();
60   }
61 
addAsyncViaFixture62   void addAsync(int a, int b, std::function<void(int&&)>&& cob) {
63     eastExecutor->add([=]() { cob(a + b); });
64   }
65 
66   std::shared_ptr<ManualExecutor> westExecutor;
67   std::shared_ptr<ManualExecutor> eastExecutor;
68   std::shared_ptr<ManualWaiter> waiter;
69   InlineExecutor inlineExecutor;
70   std::atomic<bool> done;
71   std::thread th;
72 };
73 
TEST(Via,exceptionOnLaunch)74 TEST(Via, exceptionOnLaunch) {
75   auto future = makeFuture<int>(std::runtime_error("E"));
76   EXPECT_THROW(future.value(), std::runtime_error);
77 }
78 
TEST(Via,thenValue)79 TEST(Via, thenValue) {
80   auto future = makeFuture(std::move(1)).thenTry([](Try<int>&& t) {
81     return t.value() == 1;
82   });
83 
84   EXPECT_TRUE(future.value());
85 }
86 
TEST(Via,thenFuture)87 TEST(Via, thenFuture) {
88   auto future = makeFuture(1).thenTry(
89       [](Try<int>&& t) { return makeFuture(t.value() == 1); });
90   EXPECT_TRUE(future.value());
91 }
92 
doWorkStatic(Try<std::string> && t)93 static Future<std::string> doWorkStatic(Try<std::string>&& t) {
94   return makeFuture(t.value() + ";static");
95 }
96 
TEST(Via,thenFunction)97 TEST(Via, thenFunction) {
98   struct Worker {
99     Future<std::string> doWork(Try<std::string>&& t) {
100       return makeFuture(t.value() + ";class");
101     }
102     static Future<std::string> doWorkStatic(Try<std::string>&& t) {
103       return makeFuture(t.value() + ";class-static");
104     }
105   } w;
106 
107   auto f = makeFuture(std::string("start"))
108                .thenTry(doWorkStatic)
109                .thenTry(Worker::doWorkStatic)
110                .then(&Worker::doWork, &w);
111 
112   EXPECT_EQ(f.value(), "start;static;class-static;class");
113 }
114 
TEST_F(ViaFixture,threadHops)115 TEST_F(ViaFixture, threadHops) {
116   auto westThreadId = std::this_thread::get_id();
117   auto f = via(eastExecutor.get())
118                .thenTry([=](Try<Unit>&& /* t */) {
119                  EXPECT_NE(std::this_thread::get_id(), westThreadId);
120                  return makeFuture<int>(1);
121                })
122                .via(westExecutor.get())
123                .thenTry([=](Try<int>&& t) {
124                  EXPECT_EQ(std::this_thread::get_id(), westThreadId);
125                  return t.value();
126                });
127   EXPECT_EQ(std::move(f).getVia(waiter.get()), 1);
128 }
129 
TEST_F(ViaFixture,chainVias)130 TEST_F(ViaFixture, chainVias) {
131   auto westThreadId = std::this_thread::get_id();
132   auto f = via(eastExecutor.get())
133                .thenValue([=](auto&&) {
134                  EXPECT_NE(std::this_thread::get_id(), westThreadId);
135                  return 1;
136                })
137                .thenValue([=](int val) {
138                  return makeFuture(val)
139                      .via(westExecutor.get())
140                      .thenValue([=](int v) mutable {
141                        EXPECT_EQ(std::this_thread::get_id(), westThreadId);
142                        return v + 1;
143                      });
144                })
145                .thenValue([=](int val) {
146                  // even though ultimately the future that triggers this one
147                  // executed in the west thread, this thenValue() inherited the
148                  // executor from its predecessor, ie the eastExecutor.
149                  EXPECT_NE(std::this_thread::get_id(), westThreadId);
150                  return val + 1;
151                })
152                .via(westExecutor.get())
153                .thenValue([=](int val) {
154                  // go back to west, so we can wait on it
155                  EXPECT_EQ(std::this_thread::get_id(), westThreadId);
156                  return val + 1;
157                });
158 
159   EXPECT_EQ(std::move(f).getVia(waiter.get()), 4);
160 }
161 
TEST_F(ViaFixture,bareViaAssignment)162 TEST_F(ViaFixture, bareViaAssignment) {
163   auto f = via(eastExecutor.get());
164 }
TEST_F(ViaFixture,viaAssignment)165 TEST_F(ViaFixture, viaAssignment) {
166   // via()&&
167   auto f = makeFuture().via(eastExecutor.get());
168   // via()&
169   auto f2 = f.via(eastExecutor.get());
170 }
171 
172 struct PriorityExecutor : public Executor {
addPriorityExecutor173   void add(Func /* f */) override { count1++; }
174 
addWithPriorityPriorityExecutor175   void addWithPriority(Func f, int8_t priority) override {
176     int mid = getNumPriorities() / 2;
177     int p = priority < 0 ? std::max(0, mid + priority)
178                          : std::min(getNumPriorities() - 1, mid + priority);
179     EXPECT_LT(p, 3);
180     EXPECT_GE(p, 0);
181     if (p == 0) {
182       count0++;
183     } else if (p == 1) {
184       count1++;
185     } else if (p == 2) {
186       count2++;
187     }
188     f();
189   }
190 
getNumPrioritiesPriorityExecutor191   uint8_t getNumPriorities() const override { return 3; }
192 
193   int count0{0};
194   int count1{0};
195   int count2{0};
196 };
197 
TEST(Via,priority)198 TEST(Via, priority) {
199   PriorityExecutor exe;
200   via(&exe, -1).thenValue([](auto&&) {});
201   via(&exe, 0).thenValue([](auto&&) {});
202   via(&exe, 1).thenValue([](auto&&) {});
203   via(&exe, 42).thenValue([](auto&&) {}); // overflow should go to max priority
204   via(&exe, -42).thenValue(
205       [](auto&&) {}); // underflow should go to min priority
206   via(&exe).thenValue([](auto&&) {}); // default to mid priority
207   via(&exe, Executor::LO_PRI).thenValue([](auto&&) {});
208   via(&exe, Executor::HI_PRI).thenValue([](auto&&) {});
209   EXPECT_EQ(3, exe.count0);
210   EXPECT_EQ(2, exe.count1);
211   EXPECT_EQ(3, exe.count2);
212 }
213 
TEST(Via,then2)214 TEST(Via, then2) {
215   ManualExecutor x1, x2;
216   bool a = false, b = false, c = false;
217   via(&x1)
218       .thenValue([&](auto&&) { a = true; })
219       .thenValue([&](auto&&) { b = true; })
220       .thenValueInline(folly::makeAsyncTask(&x2, [&](auto&&) { c = true; }));
221 
222   EXPECT_FALSE(a);
223   EXPECT_FALSE(b);
224 
225   x1.run();
226   EXPECT_TRUE(a);
227   EXPECT_FALSE(b);
228   EXPECT_FALSE(c);
229 
230   x1.run();
231   EXPECT_TRUE(b);
232   EXPECT_FALSE(c);
233 
234   x2.run();
235   EXPECT_TRUE(c);
236 }
237 
TEST(Via,allowInline)238 TEST(Via, allowInline) {
239   ManualExecutor x1, x2;
240   bool a = false, b = false, c = false, d = false, e = false, f = false,
241        g = false, h = false, i = false, j = false, k = false, l = false,
242        m = false, n = false, o = false, p = false, q = false, r = false;
243   via(&x1)
244       .thenValue([&](auto&&) { a = true; })
245       .thenTryInline([&](auto&&) { b = true; })
246       .thenTry([&](auto&&) { c = true; })
247       .via(&x2)
248       .thenTryInline([&](auto&&) { d = true; })
249       .thenValue([&](auto&&) {
250         e = true;
251         return via(&x2).thenValue([&](auto&&) { f = true; });
252       })
253       .thenValueInline([&](auto&&) { g = true; })
254       .thenValue([&](auto&&) {
255         h = true;
256         return via(&x1).thenValue([&](auto&&) { i = true; });
257       })
258       .thenValueInline([&](auto&&) { j = true; })
259       .semi()
260       .deferValue([&](auto&&) { k = true; })
261       .via(&x2)
262       .thenValueInline([&](auto&&) { l = true; })
263       .semi()
264       .deferValue([&](auto&&) { m = true; })
265       .via(&x1)
266       .thenValue([&](auto&&) { n = true; })
267       .semi()
268       .deferValue([&](auto&&) { o = true; })
269       .deferValue([&](auto&&) { p = true; })
270       .via(&x1)
271       .semi()
272       .deferValue([&](auto&&) { q = true; })
273       .deferValue([&](auto&&) { r = true; })
274       .via(&x2);
275 
276   EXPECT_FALSE(a);
277   EXPECT_FALSE(b);
278 
279   // Expect b to be satisfied inline with the task x1
280   x1.run();
281   EXPECT_TRUE(a);
282   EXPECT_TRUE(b);
283   EXPECT_FALSE(c);
284 
285   x1.run();
286   EXPECT_TRUE(c);
287   EXPECT_FALSE(d);
288 
289   // Demonstrate that the executor transition did not allow inline execution
290   x2.run();
291   EXPECT_TRUE(d);
292   EXPECT_FALSE(e);
293 
294   x2.run();
295   EXPECT_TRUE(e);
296   EXPECT_FALSE(f);
297   EXPECT_FALSE(g);
298 
299   // Completing nested continuation should satisfy inline continuation
300   x2.run();
301   EXPECT_TRUE(f);
302   EXPECT_TRUE(g);
303   EXPECT_FALSE(h);
304 
305   x2.run();
306   EXPECT_TRUE(h);
307   EXPECT_FALSE(i);
308   EXPECT_FALSE(j);
309 
310   // Nested continuation on different executor should not complete next entry
311   // inline
312   x1.run();
313   EXPECT_TRUE(i);
314   EXPECT_FALSE(j);
315 
316   // Defer should run on x1 and therefore not inline
317   // Subsequent deferred work is run on x1 and hence not inlined.
318   x2.run();
319   EXPECT_TRUE(j);
320   EXPECT_TRUE(k);
321   EXPECT_TRUE(l);
322   EXPECT_FALSE(m);
323 
324   // Complete the deferred task
325   x1.run();
326   EXPECT_TRUE(m);
327   EXPECT_FALSE(n);
328 
329   // Here defer and the above thenValue are both on x1, defer should be
330   // inline
331   x1.run();
332   EXPECT_TRUE(n);
333   EXPECT_TRUE(o);
334   EXPECT_TRUE(p);
335   EXPECT_FALSE(q);
336 
337   // Change of executor in deferred executor so now run x2 to complete
338   x2.run();
339   EXPECT_TRUE(q);
340   EXPECT_TRUE(r);
341 }
342 
343 #ifndef __APPLE__ // TODO #7372389
344 /// Simple executor that does work in another thread
345 class ThreadExecutor : public Executor {
346   folly::MPMCQueue<Func> funcs;
347   std::atomic<bool> done{false};
348   std::thread worker;
349   folly::Baton<> baton;
350 
work()351   void work() {
352     baton.post();
353     Func fn;
354     while (!done) {
355       while (!funcs.isEmpty()) {
356         funcs.blockingRead(fn);
357         fn();
358       }
359     }
360   }
361 
362  public:
ThreadExecutor(size_t n=1024)363   explicit ThreadExecutor(size_t n = 1024) : funcs(n) {
364     worker = std::thread(std::bind(&ThreadExecutor::work, this));
365   }
366 
~ThreadExecutor()367   ~ThreadExecutor() override {
368     done = true;
369     funcs.write([] {});
370     worker.join();
371   }
372 
add(Func fn)373   void add(Func fn) override { funcs.blockingWrite(std::move(fn)); }
374 
waitForStartup()375   void waitForStartup() { baton.wait(); }
376 };
377 
TEST(Via,viaThenGetWasRacy)378 TEST(Via, viaThenGetWasRacy) {
379   ThreadExecutor x;
380   std::unique_ptr<int> val =
381       folly::via(&x)
382           .thenValue([](auto&&) { return std::make_unique<int>(42); })
383           .get();
384   ASSERT_TRUE(!!val);
385   EXPECT_EQ(42, *val);
386 }
387 
TEST(Via,callbackRace)388 TEST(Via, callbackRace) {
389   ThreadExecutor x;
390 
391   auto fn = [&x] {
392     auto promises = std::make_shared<std::vector<Promise<Unit>>>(4);
393     std::vector<Future<Unit>> futures;
394 
395     for (auto& p : *promises) {
396       futures.emplace_back(p.getFuture().via(&x).thenTry([](Try<Unit>&&) {}));
397     }
398 
399     x.waitForStartup();
400     x.add([promises] {
401       for (auto& p : *promises) {
402         p.setValue();
403       }
404     });
405 
406     return collectAll(futures);
407   };
408 
409   fn().wait();
410 }
411 #endif
412 
413 class DummyDrivableExecutor : public DrivableExecutor {
414  public:
add(Func)415   void add(Func /* f */) override {}
drive()416   void drive() override { ran = true; }
417   bool ran{false};
418 };
419 
TEST(Via,getVia)420 TEST(Via, getVia) {
421   {
422     // non-void
423     ManualExecutor x;
424     auto f = via(&x).thenValue([](auto&&) { return true; });
425     EXPECT_TRUE(std::move(f).getVia(&x));
426   }
427 
428   {
429     // void
430     ManualExecutor x;
431     auto f = via(&x).then();
432     std::move(f).getVia(&x);
433   }
434 
435   {
436     DummyDrivableExecutor x;
437     auto f = makeFuture(true);
438     EXPECT_TRUE(std::move(f).getVia(&x));
439     EXPECT_FALSE(x.ran);
440   }
441 }
442 
TEST(Via,SimpleTimedGetVia)443 TEST(Via, SimpleTimedGetVia) {
444   TimedDrivableExecutor e2;
445   Promise<folly::Unit> p;
446   auto f = p.getFuture();
447   EXPECT_THROW(
448       std::move(f).getVia(&e2, std::chrono::seconds(1)), FutureTimeout);
449 }
450 
TEST(Via,getTryVia)451 TEST(Via, getTryVia) {
452   {
453     // non-void
454     ManualExecutor x;
455     auto f = via(&x).thenValue([](auto&&) { return 23; });
456     EXPECT_FALSE(f.isReady());
457     EXPECT_EQ(23, std::move(f).getTryVia(&x).value());
458   }
459 
460   {
461     // void
462     ManualExecutor x;
463     auto f = via(&x).then();
464     EXPECT_FALSE(f.isReady());
465     auto t = std::move(f).getTryVia(&x);
466     EXPECT_TRUE(t.hasValue());
467   }
468 
469   {
470     DummyDrivableExecutor x;
471     auto f = makeFuture(23);
472     EXPECT_EQ(23, std::move(f).getTryVia(&x).value());
473     EXPECT_FALSE(x.ran);
474   }
475 }
476 
TEST(Via,SimpleTimedGetTryVia)477 TEST(Via, SimpleTimedGetTryVia) {
478   TimedDrivableExecutor e2;
479   Promise<folly::Unit> p;
480   auto f = p.getFuture();
481   EXPECT_THROW(
482       std::move(f).getTryVia(&e2, std::chrono::seconds(1)), FutureTimeout);
483 }
484 
TEST(Via,waitVia)485 TEST(Via, waitVia) {
486   {
487     ManualExecutor x;
488     auto f = via(&x).then();
489     EXPECT_FALSE(f.isReady());
490     f.waitVia(&x);
491     EXPECT_TRUE(f.isReady());
492   }
493 
494   {
495     // try rvalue as well
496     ManualExecutor x;
497     auto f = via(&x).then().waitVia(&x);
498     EXPECT_TRUE(f.isReady());
499   }
500 
501   {
502     DummyDrivableExecutor x;
503     makeFuture(true).waitVia(&x);
504     EXPECT_FALSE(x.ran);
505   }
506 }
507 
TEST(Via,viaRaces)508 TEST(Via, viaRaces) {
509   ManualExecutor x;
510   Promise<Unit> p;
511   auto tid = std::this_thread::get_id();
512   bool done = false;
513 
514   std::thread t1([&] {
515     p.getFuture()
516         .via(&x)
517         .thenTry(
518             [&](Try<Unit>&&) { EXPECT_EQ(tid, std::this_thread::get_id()); })
519         .thenTry(
520             [&](Try<Unit>&&) { EXPECT_EQ(tid, std::this_thread::get_id()); })
521         .thenTry([&](Try<Unit>&&) { done = true; });
522   });
523 
524   std::thread t2([&] { p.setValue(); });
525 
526   while (!done) {
527     x.run();
528   }
529   t1.join();
530   t2.join();
531 }
532 
TEST(Via,viaDummyExecutorFutureSetValueFirst)533 TEST(Via, viaDummyExecutorFutureSetValueFirst) {
534   // The callback object will get destroyed when passed to the executor.
535 
536   // A promise will be captured by the callback lambda so we can observe that
537   // it will be destroyed.
538   Promise<Unit> captured_promise;
539   auto captured_promise_future = captured_promise.getFuture();
540 
541   DummyDrivableExecutor x;
542   auto future = makeFuture().via(&x).thenValue(
543       [c = std::move(captured_promise)](auto&&) { return 42; });
544 
545   EXPECT_THROW(std::move(future).get(std::chrono::seconds(5)), BrokenPromise);
546   EXPECT_THROW(
547       std::move(captured_promise_future).get(std::chrono::seconds(5)),
548       BrokenPromise);
549 }
550 
TEST(Via,viaDummyExecutorFutureSetCallbackFirst)551 TEST(Via, viaDummyExecutorFutureSetCallbackFirst) {
552   // The callback object will get destroyed when passed to the executor.
553 
554   // A promise will be captured by the callback lambda so we can observe that
555   // it will be destroyed.
556   Promise<Unit> captured_promise;
557   auto captured_promise_future = captured_promise.getFuture();
558 
559   DummyDrivableExecutor x;
560   Promise<Unit> trigger;
561   auto future = trigger.getFuture().via(&x).thenValue(
562       [c = std::move(captured_promise)](auto&&) { return 42; });
563   trigger.setValue();
564 
565   EXPECT_THROW(std::move(future).get(std::chrono::seconds(5)), BrokenPromise);
566   EXPECT_THROW(
567       std::move(captured_promise_future).get(std::chrono::seconds(5)),
568       BrokenPromise);
569 }
570 
TEST(Via,viaExecutorDiscardsTaskFutureSetValueFirst)571 TEST(Via, viaExecutorDiscardsTaskFutureSetValueFirst) {
572   // The callback object will get destroyed when the ManualExecutor runs out
573   // of scope.
574 
575   // A promise will be captured by the callback lambda so we can observe that
576   // it will be destroyed.
577   Promise<Unit> captured_promise;
578   auto captured_promise_future = captured_promise.getFuture();
579 
580   Optional<SemiFuture<int>> future;
581   {
582     ManualExecutor x;
583     future =
584         makeFuture()
585             .via(&x)
586             .thenValue([c = std::move(captured_promise)](auto&&) { return 42; })
587             .semi();
588     x.clear();
589   }
590 
591   EXPECT_THROW(std::move(*future).get(std::chrono::seconds(5)), BrokenPromise);
592   EXPECT_THROW(
593       std::move(captured_promise_future).get(std::chrono::seconds(5)),
594       BrokenPromise);
595 }
596 
TEST(Via,viaExecutorDiscardsTaskFutureSetCallbackFirst)597 TEST(Via, viaExecutorDiscardsTaskFutureSetCallbackFirst) {
598   // The callback object will get destroyed when the ManualExecutor runs out
599   // of scope.
600 
601   // A promise will be captured by the callback lambda so we can observe that
602   // it will be destroyed.
603   Promise<Unit> captured_promise;
604   auto captured_promise_future = captured_promise.getFuture();
605 
606   Optional<SemiFuture<int>> future;
607   {
608     ManualExecutor x;
609     Promise<Unit> trigger;
610     future =
611         trigger.getFuture()
612             .via(&x)
613             .thenValue([c = std::move(captured_promise)](auto&&) { return 42; })
614             .semi();
615     trigger.setValue();
616     x.clear();
617   }
618 
619   EXPECT_THROW(std::move(*future).get(std::chrono::seconds(5)), BrokenPromise);
620   EXPECT_THROW(
621       std::move(captured_promise_future).get(std::chrono::seconds(5)),
622       BrokenPromise);
623 }
624 
TEST(ViaFunc,liftsVoid)625 TEST(ViaFunc, liftsVoid) {
626   ManualExecutor x;
627   int count = 0;
628   Future<Unit> f = via(&x, [&] { count++; });
629 
630   EXPECT_EQ(0, count);
631   x.run();
632   EXPECT_EQ(1, count);
633 }
634 
TEST(ViaFunc,value)635 TEST(ViaFunc, value) {
636   ManualExecutor x;
637   EXPECT_EQ(42, via(&x, [] { return 42; }).getVia(&x));
638 }
639 
TEST(ViaFunc,exception)640 TEST(ViaFunc, exception) {
641   ManualExecutor x;
642   EXPECT_THROW(
643       via(&x, []() -> int { throw std::runtime_error("expected"); }).getVia(&x),
644       std::runtime_error);
645 }
646 
TEST(ViaFunc,future)647 TEST(ViaFunc, future) {
648   ManualExecutor x;
649   EXPECT_EQ(42, via(&x, [] { return makeFuture(42); }).getVia(&x));
650 }
651 
TEST(ViaFunc,semi_future)652 TEST(ViaFunc, semi_future) {
653   ManualExecutor x;
654   EXPECT_EQ(42, via(&x, [] { return makeSemiFuture(42); }).getVia(&x));
655 }
656 
TEST(ViaFunc,voidFuture)657 TEST(ViaFunc, voidFuture) {
658   ManualExecutor x;
659   int count = 0;
660   via(&x, [&] { count++; }).getVia(&x);
661   EXPECT_EQ(1, count);
662 }
663 
TEST(ViaFunc,isSticky)664 TEST(ViaFunc, isSticky) {
665   ManualExecutor x;
666   int count = 0;
667 
668   auto f = via(&x, [&] { count++; });
669   x.run();
670 
671   std::move(f).thenValue([&](auto&&) { count++; });
672   EXPECT_EQ(1, count);
673   x.run();
674   EXPECT_EQ(2, count);
675 }
676 
TEST(ViaFunc,moveOnly)677 TEST(ViaFunc, moveOnly) {
678   ManualExecutor x;
679   auto intp = std::make_unique<int>(42);
680 
681   EXPECT_EQ(42, via(&x, [intp = std::move(intp)] { return *intp; }).getVia(&x));
682 }
683 
TEST(ViaFunc,valueKeepAlive)684 TEST(ViaFunc, valueKeepAlive) {
685   ManualExecutor x;
686   EXPECT_EQ(42, via(getKeepAliveToken(&x), [] { return 42; }).getVia(&x));
687 }
688 
TEST(ViaFunc,thenValueKeepAlive)689 TEST(ViaFunc, thenValueKeepAlive) {
690   ManualExecutor x;
691   EXPECT_EQ(
692       42,
693       via(getKeepAliveToken(&x))
694           .thenValue([](auto&&) { return 42; })
695           .getVia(&x));
696 }
697