summaryrefslogtreecommitdiff
path: root/indra/llcommon/tests/llpounceable_test.cpp
blob: b3024966c5f61610e072692dbc388d1e3ed22b63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/**
 * @file   llpounceable_test.cpp
 * @author Nat Goodspeed
 * @date   2015-05-22
 * @brief  Test for llpounceable.
 *
 * $LicenseInfo:firstyear=2015&license=viewerlgpl$
 * Copyright (c) 2015, Linden Research, Inc.
 * $/LicenseInfo$
 */

// Precompiled header
#include "linden_common.h"
// associated header
#include "llpounceable.h"
// STL headers
// std headers
// external library headers
#include <boost/bind.hpp>
// other Linden headers
#include "../test/lltut.h"

/*----------------------------- string testing -----------------------------*/
void append(std::string* dest, const std::string& src)
{
    dest->append(src);
}

/*-------------------------- Data-struct testing ---------------------------*/
struct Data
{
    Data(const std::string& data):
        mData(data)
    {}
    const std::string mData;
};

void setter(Data** dest, Data* ptr)
{
    *dest = ptr;
}

static Data* static_check = 0;

// Set up an extern pointer to an LLPounceableStatic so the linker will fill
// in the forward reference from below, before runtime.
extern LLPounceable<Data*, LLPounceableStatic> gForward;

struct EnqueueCall
{
    EnqueueCall()
    {
        // Intentionally use a forward reference to an LLPounceableStatic that
        // we believe is NOT YET CONSTRUCTED. This models the scenario in
        // which a constructor in another translation unit runs before
        // constructors in this one. We very specifically want callWhenReady()
        // to work even in that case: we need the LLPounceableQueueImpl to be
        // initialized even if the LLPounceable itself is not.
        gForward.callWhenReady(boost::bind(setter, &static_check, _1));
    }
} nqcall;
// When this declaration is processed, we should enqueue the
// setter(&static_check, _1) call for when gForward is set non-NULL. Needless
// to remark, we want this call not to crash.

// Now declare gForward. Its constructor should not run until after nqcall's.
LLPounceable<Data*, LLPounceableStatic> gForward;

/*****************************************************************************
*   TUT
*****************************************************************************/
namespace tut
{
    struct llpounceable_data
    {
    };
    typedef test_group<llpounceable_data> llpounceable_group;
    typedef llpounceable_group::object object;
    llpounceable_group llpounceablegrp("llpounceable");

    template<> template<>
    void object::test<1>()
    {
        set_test_name("LLPounceableStatic out-of-order test");
        // LLPounceable<T, LLPounceableStatic>::callWhenReady() must work even
        // before LLPounceable's constructor runs. That's the whole point of
        // implementing it with an LLSingleton queue. This models (say)
        // LLPounceableStatic<LLMessageSystem*, LLPounceableStatic>.
        ensure("static_check should still be null", ! static_check);
        Data myData("test<1>");
        gForward = &myData;         // should run setter
        ensure_equals("static_check should be &myData", static_check, &myData);
    }

    template<> template<>
    void object::test<2>()
    {
        set_test_name("LLPounceableQueue different queues");
        // We expect that LLPounceable<T, LLPounceableQueue> should have
        // different queues because that specialization stores the queue
        // directly in the LLPounceable instance.
        Data *aptr = 0, *bptr = 0;
        LLPounceable<Data*> a, b;
        a.callWhenReady(boost::bind(setter, &aptr, _1));
        b.callWhenReady(boost::bind(setter, &bptr, _1));
        ensure("aptr should be null", ! aptr);
        ensure("bptr should be null", ! bptr);
        Data adata("a"), bdata("b");
        a = &adata;
        ensure_equals("aptr should be &adata", aptr, &adata);
        // but we haven't yet set b
        ensure("bptr should still be null", !bptr);
        b = &bdata;
        ensure_equals("bptr should be &bdata", bptr, &bdata);
    }

    template<> template<>
    void object::test<3>()
    {
        set_test_name("LLPounceableStatic different queues");
        // LLPounceable<T, LLPounceableStatic> should also have a distinct
        // queue for each instance, but that engages an additional map lookup
        // because there's only one LLSingleton for each T.
        Data *aptr = 0, *bptr = 0;
        LLPounceable<Data*, LLPounceableStatic> a, b;
        a.callWhenReady(boost::bind(setter, &aptr, _1));
        b.callWhenReady(boost::bind(setter, &bptr, _1));
        ensure("aptr should be null", ! aptr);
        ensure("bptr should be null", ! bptr);
        Data adata("a"), bdata("b");
        a = &adata;
        ensure_equals("aptr should be &adata", aptr, &adata);
        // but we haven't yet set b
        ensure("bptr should still be null", !bptr);
        b = &bdata;
        ensure_equals("bptr should be &bdata", bptr, &bdata);
    }

    template<> template<>
    void object::test<4>()
    {
        set_test_name("LLPounceable<T> looks like T");
        // We want LLPounceable<T, TAG> to be drop-in replaceable for a plain
        // T for read constructs. In particular, it should behave like a dumb
        // pointer -- and with zero abstraction cost for such usage.
        Data* aptr = 0;
        Data a("a");
        // should be able to initialize a pounceable (when its constructor
        // runs)
        LLPounceable<Data*> pounceable(&a);
        // should be able to pass LLPounceable<T> to function accepting T
        setter(&aptr, pounceable);
        ensure_equals("aptr should be &a", aptr, &a);
        // should be able to dereference with *
        ensure_equals("deref with *", (*pounceable).mData, "a");
        // should be able to dereference with ->
        ensure_equals("deref with ->", pounceable->mData, "a");
        // bool operations
        ensure("test with operator bool()", pounceable);
        ensure("test with operator !()", ! (! pounceable));
    }

    template<> template<>
    void object::test<5>()
    {
        set_test_name("Multiple callWhenReady() queue items");
        Data *p1 = 0, *p2 = 0, *p3 = 0;
        Data a("a");
        LLPounceable<Data*> pounceable;
        // queue up a couple setter() calls for later
        pounceable.callWhenReady(boost::bind(setter, &p1, _1));
        pounceable.callWhenReady(boost::bind(setter, &p2, _1));
        // should still be pending
        ensure("p1 should be null", !p1);
        ensure("p2 should be null", !p2);
        ensure("p3 should be null", !p3);
        pounceable = 0;
        // assigning a new empty value shouldn't flush the queue
        ensure("p1 should still be null", !p1);
        ensure("p2 should still be null", !p2);
        ensure("p3 should still be null", !p3);
        // using whichever syntax
        pounceable.reset(0);
        // try to make ensure messages distinct... tough to pin down which
        // ensure() failed if multiple ensure() calls in the same test<n> have
        // the same message!
        ensure("p1 should again be null", !p1);
        ensure("p2 should again be null", !p2);
        ensure("p3 should again be null", !p3);
        pounceable.reset(&a);       // should flush queue
        ensure_equals("p1 should be &a", p1, &a);
        ensure_equals("p2 should be &a", p2, &a);
        ensure("p3 still not set", !p3);
        // immediate call
        pounceable.callWhenReady(boost::bind(setter, &p3, _1));
        ensure_equals("p3 should be &a", p3, &a);
    }

    template<> template<>
    void object::test<6>()
    {
        set_test_name("queue order");
        std::string data;
        LLPounceable<std::string*> pounceable;
        pounceable.callWhenReady(boost::bind(append, _1, "a"));
        pounceable.callWhenReady(boost::bind(append, _1, "b"));
        pounceable.callWhenReady(boost::bind(append, _1, "c"));
        pounceable = &data;
        ensure_equals("callWhenReady() must preserve chronological order",
                      data, "abc");

        std::string data2;
        pounceable = NULL;
        pounceable.callWhenReady(boost::bind(append, _1, "d"));
        pounceable.callWhenReady(boost::bind(append, _1, "e"));
        pounceable.callWhenReady(boost::bind(append, _1, "f"));
        pounceable = &data2;
        ensure_equals("LLPounceable must reset queue when fired",
                      data2, "def");
    }

    template<> template<>
    void object::test<7>()
    {
        set_test_name("compile-fail test, uncomment to check");
        // The following declaration should fail: only LLPounceableQueue and
        // LLPounceableStatic should work as tags.
//      LLPounceable<Data*, int> pounceable;
    }
} // namespace tut