1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <map>
6 
7 #include <lib/fit/promise.h>
8 #include <unittest/unittest.h>
9 
10 #include "unittest_utils.h"
11 
12 namespace {
13 
14 enum class disposition {
15     pending,
16     resumed,
17     released
18 };
19 
20 class fake_resolver : public fit::suspended_task::resolver {
21 public:
num_tickets_issued() const22     uint64_t num_tickets_issued() const { return next_ticket_ - 1; }
23 
obtain_ticket()24     fit::suspended_task::ticket obtain_ticket() {
25         fit::suspended_task::ticket ticket = next_ticket_++;
26         tickets_.emplace(ticket, disposition::pending);
27         return ticket;
28     }
29 
get_disposition(fit::suspended_task::ticket ticket)30     disposition get_disposition(fit::suspended_task::ticket ticket) {
31         auto it = tickets_.find(ticket);
32         ASSERT_CRITICAL(it != tickets_.end());
33         return it->second;
34     }
35 
duplicate_ticket(fit::suspended_task::ticket ticket)36     fit::suspended_task::ticket duplicate_ticket(
37         fit::suspended_task::ticket ticket) override {
38         auto it = tickets_.find(ticket);
39         ASSERT_CRITICAL(it != tickets_.end());
40         ASSERT_CRITICAL(it->second == disposition::pending);
41         return obtain_ticket();
42     }
43 
resolve_ticket(fit::suspended_task::ticket ticket,bool resume_task)44     void resolve_ticket(
45         fit::suspended_task::ticket ticket, bool resume_task) override {
46         auto it = tickets_.find(ticket);
47         ASSERT_CRITICAL(it != tickets_.end());
48         ASSERT_CRITICAL(it->second == disposition::pending);
49         it->second = resume_task ? disposition::resumed : disposition::released;
50     }
51 
52 private:
53     fit::suspended_task::ticket next_ticket_ = 1;
54     std::map<fit::suspended_task::ticket, disposition> tickets_;
55 };
56 
test()57 bool test() {
58     BEGIN_TEST;
59 
60     fake_resolver resolver;
61     {
62         fit::suspended_task empty1;
63         EXPECT_FALSE(empty1);
64 
65         fit::suspended_task empty2(nullptr, 42);
66         EXPECT_FALSE(empty2);
67 
68         fit::suspended_task empty_copy(empty1);
69         EXPECT_FALSE(empty_copy);
70         EXPECT_FALSE(empty1);
71 
72         fit::suspended_task empty_move(std::move(empty2));
73         EXPECT_FALSE(empty_move);
74         EXPECT_FALSE(empty2);
75 
76         fit::suspended_task task(&resolver, resolver.obtain_ticket());
77         EXPECT_TRUE(task);
78         EXPECT_EQ(1, resolver.num_tickets_issued());
79         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
80 
81         fit::suspended_task task_copy(task);
82         EXPECT_TRUE(task_copy);
83         EXPECT_TRUE(task);
84         EXPECT_EQ(2, resolver.num_tickets_issued());
85         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
86         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
87 
88         fit::suspended_task task_move(std::move(task));
89         EXPECT_TRUE(task_move);
90         EXPECT_FALSE(task);
91         EXPECT_EQ(2, resolver.num_tickets_issued());
92         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
93         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
94 
95         fit::suspended_task x;
96         x = empty1;
97         EXPECT_FALSE(x);
98 
99         x = task_copy;
100         EXPECT_TRUE(x);
101         EXPECT_TRUE(task_copy);
102         EXPECT_EQ(3, resolver.num_tickets_issued());
103         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
104         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
105         EXPECT_EQ(disposition::pending, resolver.get_disposition(3));
106 
107         x = std::move(empty_move); // x's ticket is released here
108         EXPECT_FALSE(x);
109         EXPECT_FALSE(empty_move);
110         EXPECT_EQ(3, resolver.num_tickets_issued());
111         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
112         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
113         EXPECT_EQ(disposition::released, resolver.get_disposition(3));
114 
115         x = task_copy;            // assign x a duplicate ticket
116         x = std::move(task_move); // x's ticket is released here
117         EXPECT_TRUE(x);
118         EXPECT_TRUE(task_copy);
119         EXPECT_FALSE(task_move);
120         EXPECT_EQ(4, resolver.num_tickets_issued());
121         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
122         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
123         EXPECT_EQ(disposition::released, resolver.get_disposition(3));
124         EXPECT_EQ(disposition::released, resolver.get_disposition(4));
125 
126         x.resume_task(); // x's ticket is resumed here
127         EXPECT_FALSE(x);
128         EXPECT_EQ(4, resolver.num_tickets_issued());
129         EXPECT_EQ(disposition::resumed, resolver.get_disposition(1));
130         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
131         EXPECT_EQ(disposition::released, resolver.get_disposition(3));
132         EXPECT_EQ(disposition::released, resolver.get_disposition(4));
133 
134         x.resume_task(); // already resumed so has no effect
135         EXPECT_FALSE(x);
136 
137         x.reset(); // already resumed so has no effect
138         EXPECT_FALSE(x);
139 
140         // note: task_copy still has a ticket here which will be
141         // released when the scope exits
142     }
143     EXPECT_EQ(4, resolver.num_tickets_issued());
144     EXPECT_EQ(disposition::resumed, resolver.get_disposition(1));
145     EXPECT_EQ(disposition::released, resolver.get_disposition(2));
146     EXPECT_EQ(disposition::released, resolver.get_disposition(3));
147     EXPECT_EQ(disposition::released, resolver.get_disposition(4));
148 
149     END_TEST;
150 }
151 
swapping()152 bool swapping() {
153     BEGIN_TEST;
154 
155     fake_resolver resolver;
156     {
157         fit::suspended_task a(&resolver, resolver.obtain_ticket());
158         fit::suspended_task b(&resolver, resolver.obtain_ticket());
159         fit::suspended_task c;
160         EXPECT_EQ(2, resolver.num_tickets_issued());
161         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
162         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
163 
164         a.swap(c);
165         EXPECT_FALSE(a);
166         EXPECT_TRUE(c);
167         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
168         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
169 
170         a.swap(a);
171         EXPECT_FALSE(a);
172         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
173         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
174 
175         swap(c, b);
176         EXPECT_TRUE(c);
177         EXPECT_TRUE(b);
178         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
179         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
180 
181         swap(c, c);
182         EXPECT_TRUE(c);
183         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
184         EXPECT_EQ(disposition::pending, resolver.get_disposition(2));
185 
186         c.resume_task();
187         EXPECT_FALSE(c);
188         EXPECT_EQ(disposition::pending, resolver.get_disposition(1));
189         EXPECT_EQ(disposition::resumed, resolver.get_disposition(2));
190 
191         b.reset();
192         EXPECT_FALSE(b);
193         EXPECT_EQ(disposition::released, resolver.get_disposition(1));
194         EXPECT_EQ(disposition::resumed, resolver.get_disposition(2));
195     }
196 
197     END_TEST;
198 }
199 
200 } // namespace
201 
202 BEGIN_TEST_CASE(suspended_task_tests)
203 RUN_TEST(test)
204 RUN_TEST(swapping)
205 END_TEST_CASE(suspended_task_tests)
206