ccsession_unittests.cc 47.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

JINMEI Tatuya's avatar
JINMEI Tatuya committed
15
#include <config.h>
16

17
18
#include <gtest/gtest.h>

JINMEI Tatuya's avatar
JINMEI Tatuya committed
19
#include <config/tests/fake_session.h>
20
21
22
23
24

#include <config/ccsession.h>

#include <fstream>

25
#include <config/tests/data_def_unittests_config.h>
26

27
28
#include <log/logger_name.h>

29
#include <boost/scoped_ptr.hpp>
30
#include <boost/bind.hpp>
31

32
33
using namespace isc::data;
using namespace isc::config;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
34
using namespace isc::cc;
35
36
using namespace std;

JINMEI Tatuya's avatar
JINMEI Tatuya committed
37
38
namespace {
std::string
39
ccspecfile(const std::string& name) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
40
    return (std::string(TEST_DATA_PATH) + "/" + name);
41
42
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
43
44
ElementPtr
el(const std::string& str) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
45
    return (Element::fromJSON(str));
46
47
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
48
49
class CCSessionTest : public ::testing::Test {
protected:
50
51
52
    CCSessionTest() : session(el("[]"), el("[]"), el("[]")),
                      root_name(isc::log::getRootLoggerName())
    {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
53
        // upon creation of a ModuleCCSession, the class
54

JINMEI Tatuya's avatar
JINMEI Tatuya committed
55
        // sends its specification to the config manager.
56
        // it expects an ok answer back, so every time we
JINMEI Tatuya's avatar
JINMEI Tatuya committed
57
58
59
        // create a ModuleCCSession, we must set an initial
        // ok answer.
        session.getMessages()->add(createAnswer());
60
    }
61
62
63
64
    ConstElementPtr rpcCheck(const std::string& reply) {
        ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL,
                             false, false);
        // Prepare the answer beforehand, it'll block until it gets one
65
        const ConstElementPtr reply_el(el(reply));
66
        session.getMessages()->add(reply_el);
67
68
69
70
71
72
73
74
75
76
        const ConstElementPtr
            result(mccs.rpcCall("test", "Spec2",
                                el("{\"param1\": \"Param 1\","
                                   "\"param2\": \"Param 2\"}")));
        const ConstElementPtr
            request(el("[\"Spec2\", \"*\", {"
                       "  \"command\": [\"test\", {"
                       "    \"param1\": \"Param 1\","
                       "    \"param2\": \"Param 2\""
                       "}]}, -1, true]"));
77
78
79
80
81
82
        // The 0th one is from the initialization, to ConfigManager.
        // our is the 1st.
        EXPECT_TRUE(request->equals(*session.getMsgQueue()->get(1))) <<
            session.getMsgQueue()->get(1)->toWire();
        return (result);
    }
83
84
    ~CCSessionTest() {
        isc::log::setRootLoggerName(root_name);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
85
86
    }
    FakeSession session;
87
    const std::string root_name;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
88
};
89

90
91
92
// Test we can send an RPC (command) and get an answer. The answer is success
// in this case.
TEST_F(CCSessionTest, rpcCallSuccess) {
93
    const ConstElementPtr result =
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
        rpcCheck("{\"result\": [0, {\"Hello\": \"a\"}]}");
    EXPECT_TRUE(el("{\"Hello\": \"a\"}")->equals(*result));
}

// Test success of RPC, but the answer is empty (eg. a void function on the
// remote side).
TEST_F(CCSessionTest, rpcCallSuccessNone) {
    EXPECT_FALSE(rpcCheck("{\"result\": [0]}"));
}

// Test it successfully raises CCSessionError if the answer is malformed.
TEST_F(CCSessionTest, rpcCallMalformedAnswer) {
    EXPECT_THROW(rpcCheck("[\"Nonsense\"]"), CCSessionError);
}

// Test it raises exception when the remote side reports an error
TEST_F(CCSessionTest, rpcCallError) {
    EXPECT_THROW(rpcCheck("{\"result\": [1, \"Error\"]}"), RPCError);
}

// Test it raises exception when the remote side doesn't exist
TEST_F(CCSessionTest, rpcNoRecpt) {
    EXPECT_THROW(rpcCheck("{\"result\": [-1, \"Error\"]}"),
                 RPCRecipientMissing);
}

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Test sending a notification
TEST_F(CCSessionTest, notify) {
    ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
                         false);
    mccs.notify("group", "event", el("{\"param\": true}"));
    const ConstElementPtr notification(el(
        "["
        "   \"notifications/group\","
        "   \"*\","
        "   {"
        "       \"notification\": ["
        "           \"event\", {"
        "               \"param\": true"
        "           }"
        "       ]"
        "   },"
        "   -1"
        "]"));
    EXPECT_TRUE(notification->equals(*session.getMsgQueue()->get(1))) <<
            session.getMsgQueue()->get(1)->toWire();
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
}

// Test sending a notification
TEST_F(CCSessionTest, notifyNoParams) {
    ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
                         false);
    mccs.notify("group", "event");
    const ConstElementPtr notification(el(
        "["
        "   \"notifications/group\","
        "   \"*\","
        "   {"
        "       \"notification\": [\"event\"]"
        "   },"
        "   -1"
        "]"));
    EXPECT_TRUE(notification->equals(*session.getMsgQueue()->get(1))) <<
            session.getMsgQueue()->get(1)->toWire();
158
159
}

160
161
162
163
164
165
166
167
168
169
170
// Try to subscribe and unsubscribe once again
TEST_F(CCSessionTest, subscribe) {
    ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
                         false);
    EXPECT_FALSE(session.haveSubscription("A group", "*"));
    mccs.subscribe("A group");
    EXPECT_TRUE(session.haveSubscription("A group", "*"));
    mccs.unsubscribe("A group");
    EXPECT_FALSE(session.haveSubscription("A group", "*"));
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
171
TEST_F(CCSessionTest, createAnswer) {
172
    ConstElementPtr answer;
173
    answer = createAnswer();
174
    EXPECT_EQ("{ \"result\": [ 0 ] }", answer->str());
175
    answer = createAnswer(1, "error");
176
    EXPECT_EQ("{ \"result\": [ 1, \"error\" ] }", answer->str());
177
178
179

    EXPECT_THROW(createAnswer(1, ElementPtr()), CCSessionError);
    EXPECT_THROW(createAnswer(1, Element::create(1)), CCSessionError);
180

181
    ConstElementPtr arg = el("[ \"just\", \"some\", \"data\" ]");
182
    answer = createAnswer(0, arg);
183
    EXPECT_EQ("{ \"result\": [ 0, [ \"just\", \"some\", \"data\" ] ] }", answer->str());
184
185
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
186
TEST_F(CCSessionTest, parseAnswer) {
187
188
    ConstElementPtr answer;
    ConstElementPtr arg;
189
190
    int rcode;

191
192
193
    EXPECT_THROW(parseAnswer(rcode, ElementPtr()), CCSessionError);
    EXPECT_THROW(parseAnswer(rcode, el("1")), CCSessionError);
    EXPECT_THROW(parseAnswer(rcode, el("[]")), CCSessionError);
194
    EXPECT_THROW(parseAnswer(rcode, el("{  }")), CCSessionError);
195
196
197
    EXPECT_THROW(parseAnswer(rcode, el("{ \"something\": 1 }")), CCSessionError);
    EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": 0 }")), CCSessionError);
    EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": 1 }")), CCSessionError);
198
199
    EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1 ] }")), CCSessionError);
    EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1, 1 ] }")), CCSessionError);
200
    
201
    answer = el("{ \"result\": [ 0 ] }");
202
203
204
205
    arg = parseAnswer(rcode, answer);
    EXPECT_EQ(0, rcode);
    EXPECT_TRUE(isNull(arg));

206
    answer = el("{ \"result\": [ 1, \"error\"] }");
207
208
209
210
    arg = parseAnswer(rcode, answer);
    EXPECT_EQ(1, rcode);
    EXPECT_EQ("error", arg->stringValue());

211
    answer = el("{ \"result\": [ 0, [ \"just\", \"some\", \"data\" ] ] }");
212
213
214
215
216
    arg = parseAnswer(rcode, answer);
    EXPECT_EQ(0, rcode);
    EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", arg->str());
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
217
TEST_F(CCSessionTest, createCommand) {
218
219
    ConstElementPtr command;
    ConstElementPtr arg;
220

221
    command = createCommand("my_command");
222
    ASSERT_EQ("{ \"command\": [ \"my_command\" ] }", command->str());
223
224
225

    arg = el("1");
    command = createCommand("my_command", arg);
226
    ASSERT_EQ("{ \"command\": [ \"my_command\", 1 ] }", command->str());
227
228
229

    arg = el("[ \"a\", \"b\" ]");
    command = createCommand("my_cmd", arg);
230
    ASSERT_EQ("{ \"command\": [ \"my_cmd\", [ \"a\", \"b\" ] ] }", command->str());
231
232
233

    arg = el("{ \"a\": \"map\" }");
    command = createCommand("foo", arg);
234
    ASSERT_EQ("{ \"command\": [ \"foo\", { \"a\": \"map\" } ] }", command->str());
235
236
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
237
TEST_F(CCSessionTest, parseCommand) {
238
    ConstElementPtr arg;
239
240
241
    std::string cmd;

    // should throw
242
243
    EXPECT_THROW(parseCommand(arg, ElementPtr()), CCSessionError);
    EXPECT_THROW(parseCommand(arg, el("1")), CCSessionError);
244
245
246
247
248
    EXPECT_THROW(parseCommand(arg, el("{  }")), CCSessionError);
    EXPECT_THROW(parseCommand(arg, el("{ \"not a command\": 1 }")), CCSessionError);
    EXPECT_THROW(parseCommand(arg, el("{ \"command\": 1 }")), CCSessionError);
    EXPECT_THROW(parseCommand(arg, el("{ \"command\": [] }")), CCSessionError);
    EXPECT_THROW(parseCommand(arg, el("{ \"command\": [ 1 ] }")), CCSessionError);
249

250
    cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\" ] }"));
251
    EXPECT_EQ("my_command", cmd);
252
    EXPECT_EQ(*arg, *Element::createMap());
253

254
    cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\", 1 ] }"));
255
256
257
    EXPECT_EQ("my_command", cmd);
    EXPECT_EQ("1", arg->str());

258
    parseCommand(arg, el("{ \"command\": [ \"my_command\", [ \"some\", \"argument\", \"list\" ] ] }"));
259
260
261
262
263
    EXPECT_EQ("my_command", cmd);
    EXPECT_EQ("[ \"some\", \"argument\", \"list\" ]", arg->str());

}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
264
TEST_F(CCSessionTest, session1) {
265
    EXPECT_FALSE(session.haveSubscription("Spec1", "*"));
266
267
    ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL,
                         true, false);
268
    EXPECT_TRUE(session.haveSubscription("Spec1", "*"));
269

JINMEI Tatuya's avatar
JINMEI Tatuya committed
270
    EXPECT_EQ(1, session.getMsgQueue()->size());
271
    ConstElementPtr msg;
272
    std::string group, to;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
273
    msg = session.getFirstMessage(group, to);
274
    EXPECT_EQ("{ \"command\": [ \"module_spec\", { \"module_name\": \"Spec1\" } ] }", msg->str());
275
276
    EXPECT_EQ("ConfigManager", group);
    EXPECT_EQ("*", to);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
277
    EXPECT_EQ(0, session.getMsgQueue()->size());
278

279
    // with this argument, the session should not automatically
280
281
    // subscribe to logging config
    EXPECT_FALSE(session.haveSubscription("Logging", "*"));
282
283
}

284
TEST_F(CCSessionTest, session2) {
285
    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
286
287
    ModuleCCSession mccs(ccspecfile("spec2.spec"), session, NULL, NULL,
                         true, false);
288
    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
289

JINMEI Tatuya's avatar
JINMEI Tatuya committed
290
    EXPECT_EQ(1, session.getMsgQueue()->size());
291
    ConstElementPtr msg;
292
    std::string group, to;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
293
    msg = session.getFirstMessage(group, to);
294
    EXPECT_EQ("{ \"command\": [ \"module_spec\", { \"commands\": [ { \"command_args\": [ { \"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": false, \"item_type\": \"string\" } ], \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\" }, { \"command_args\": [  ], \"command_description\": \"Shut down BIND 10\", \"command_name\": \"shutdown\" } ], \"config_data\": [ { \"item_default\": 1, \"item_name\": \"item1\", \"item_optional\": false, \"item_type\": \"integer\" }, { \"item_default\": 1.1, \"item_name\": \"item2\", \"item_optional\": false, \"item_type\": \"real\" }, { \"item_default\": true, \"item_name\": \"item3\", \"item_optional\": false, \"item_type\": \"boolean\" }, { \"item_default\": \"test\", \"item_name\": \"item4\", \"item_optional\": false, \"item_type\": \"string\" }, { \"item_default\": [ \"a\", \"b\" ], \"item_name\": \"item5\", \"item_optional\": false, \"item_type\": \"list\", \"list_item_spec\": { \"item_default\": \"\", \"item_name\": \"list_element\", \"item_optional\": false, \"item_type\": \"string\" } }, { \"item_default\": {  }, \"item_name\": \"item6\", \"item_optional\": false, \"item_type\": \"map\", \"map_item_spec\": [ { \"item_default\": \"default\", \"item_name\": \"value1\", \"item_optional\": true, \"item_type\": \"string\" }, { \"item_name\": \"value2\", \"item_optional\": true, \"item_type\": \"integer\" } ] } ], \"module_name\": \"Spec2\", \"statistics\": [ { \"item_default\": \"1970-01-01T00:00:00Z\", \"item_description\": \"A dummy date time\", \"item_format\": \"date-time\", \"item_name\": \"dummy_time\", \"item_optional\": false, \"item_title\": \"Dummy Time\", \"item_type\": \"string\" } ] } ] }", msg->str());
295
296
    EXPECT_EQ("ConfigManager", group);
    EXPECT_EQ("*", to);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
297
    EXPECT_EQ(0, session.getMsgQueue()->size());
298
299
}

300
301
302
303
304
305
306
TEST_F(CCSessionTest, session_close) {
    // Test whether ModuleCCSession automatically sends a 'stopping'
    // message when it is destroyed
    ConstElementPtr msg;
    std::string group, to;

    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
307
308
309
310
311
312
313
314
315
316
317
318
319
320

    boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
                                         ccspecfile("spec2.spec"),
                                         session, NULL, NULL,
                                         true, false));
    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
    // The initial message is irrelevant for this test
    // (see session2 test), drop it
    session.getFirstMessage(group, to);
    // Queue should now be empty
    ASSERT_EQ(0, session.getMsgQueue()->size());
    // Invoke the destructor
    mccs.reset();
    // Destructor should have caused a new message
321
322
323
324
325
326
327
328
329
    ASSERT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
    EXPECT_EQ("{ \"command\": [ \"stopping\", "
              "{ \"module_name\": \"Spec2\" } ] }", msg->str());
    EXPECT_EQ("ConfigManager", group);
    EXPECT_EQ("*", to);
    EXPECT_EQ(0, session.getMsgQueue()->size());
}

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
TEST_F(CCSessionTest, session_close_exception) {
    // Test whether an exception encountered during the destructor is
    // handled correctly
    ConstElementPtr msg;
    std::string group, to;

    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));

    boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
                                         ccspecfile("spec2.spec"),
                                         session, NULL, NULL,
                                         true, false));
    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
    // The initial message is irrelevant for this test
    // (see session2 test), drop it
    session.getFirstMessage(group, to);
    // Queue should now be empty
    ASSERT_EQ(0, session.getMsgQueue()->size());

    // Set fake session to throw an exception
    session.setThrowOnSend(true);

    // Invoke the destructor
    mccs.reset();
    // Destructor should not have caused a new message (since fakesession
    // should have thrown an exception)
    ASSERT_EQ(0, session.getMsgQueue()->size());
    //EXPECT_EQ(0, session.getMsgQueue()->size());
}


361
ConstElementPtr my_config_handler(ConstElementPtr new_config) {
362
363
    if (new_config && new_config->contains("item1") &&
        new_config->get("item1")->intValue() == 5) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
364
        return (createAnswer(6, "I do not like the number 5"));
365
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
366
    return (createAnswer());
367
368
}

369
ConstElementPtr my_command_handler(const std::string& command,
370
                                   ConstElementPtr arg)
371
372
{
    if (command == "good_command") {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
373
        return (createAnswer());
374
    } else if (command == "command_with_arg") {
375
376
        if (arg->contains("number")) {
            if (arg->get("number")->getType() == Element::integer) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
377
                return (createAnswer(0, el("2")));
378
            } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
379
                return (createAnswer(1, "arg bad type"));
380
381
            }
        } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
382
            return (createAnswer(1, "arg missing"));
383
384
        }
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
385
        return (createAnswer(1, "bad command"));
386
387
388
    }
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
389
TEST_F(CCSessionTest, session3) {
390
    // client will ask for config
JINMEI Tatuya's avatar
JINMEI Tatuya committed
391
    session.getMessages()->add(createAnswer(0, el("{}")));
392

393
    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
JINMEI Tatuya's avatar
JINMEI Tatuya committed
394
    ModuleCCSession mccs(ccspecfile("spec2.spec"), session, my_config_handler,
395
                         my_command_handler, true, false);
396
    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
397

JINMEI Tatuya's avatar
JINMEI Tatuya committed
398
    EXPECT_EQ(2, session.getMsgQueue()->size());
399
    ConstElementPtr msg;
400
    std::string group, to;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
401
    msg = session.getFirstMessage(group, to);
402
    EXPECT_EQ("{ \"command\": [ \"module_spec\", { \"commands\": [ { \"command_args\": [ { \"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": false, \"item_type\": \"string\" } ], \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\" }, { \"command_args\": [  ], \"command_description\": \"Shut down BIND 10\", \"command_name\": \"shutdown\" } ], \"config_data\": [ { \"item_default\": 1, \"item_name\": \"item1\", \"item_optional\": false, \"item_type\": \"integer\" }, { \"item_default\": 1.1, \"item_name\": \"item2\", \"item_optional\": false, \"item_type\": \"real\" }, { \"item_default\": true, \"item_name\": \"item3\", \"item_optional\": false, \"item_type\": \"boolean\" }, { \"item_default\": \"test\", \"item_name\": \"item4\", \"item_optional\": false, \"item_type\": \"string\" }, { \"item_default\": [ \"a\", \"b\" ], \"item_name\": \"item5\", \"item_optional\": false, \"item_type\": \"list\", \"list_item_spec\": { \"item_default\": \"\", \"item_name\": \"list_element\", \"item_optional\": false, \"item_type\": \"string\" } }, { \"item_default\": {  }, \"item_name\": \"item6\", \"item_optional\": false, \"item_type\": \"map\", \"map_item_spec\": [ { \"item_default\": \"default\", \"item_name\": \"value1\", \"item_optional\": true, \"item_type\": \"string\" }, { \"item_name\": \"value2\", \"item_optional\": true, \"item_type\": \"integer\" } ] } ], \"module_name\": \"Spec2\", \"statistics\": [ { \"item_default\": \"1970-01-01T00:00:00Z\", \"item_description\": \"A dummy date time\", \"item_format\": \"date-time\", \"item_name\": \"dummy_time\", \"item_optional\": false, \"item_title\": \"Dummy Time\", \"item_type\": \"string\" } ] } ] }", msg->str());
403
404
    EXPECT_EQ("ConfigManager", group);
    EXPECT_EQ("*", to);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
405
406
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
407
    EXPECT_EQ("{ \"command\": [ \"get_config\", { \"module_name\": \"Spec2\" } ] }", msg->str());
408
409
    EXPECT_EQ("ConfigManager", group);
    EXPECT_EQ("*", to);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
410
    EXPECT_EQ(0, session.getMsgQueue()->size());
411
412
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
413
TEST_F(CCSessionTest, checkCommand) {
414
    // client will ask for config
JINMEI Tatuya's avatar
JINMEI Tatuya committed
415
    session.getMessages()->add(createAnswer(0, el("{}")));
416

417
418
    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler,
419
                         my_command_handler, true, false);
420
    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
421

JINMEI Tatuya's avatar
JINMEI Tatuya committed
422
    EXPECT_EQ(2, session.getMsgQueue()->size());
423
    ConstElementPtr msg;
424
425
    std::string group, to;
    // checked above, drop em
JINMEI Tatuya's avatar
JINMEI Tatuya committed
426
427
    msg = session.getFirstMessage(group, to);
    msg = session.getFirstMessage(group, to);
428
429
430
431
432

    int result;
    result = mccs.checkCommand();
    EXPECT_EQ(0, result);

433
    // not a command, should be ignored
434
    session.addMessage(el("1"), "Spec29", "*");
435
436
    result = mccs.checkCommand();
    EXPECT_EQ(0, result);
437
    session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec29",
JINMEI Tatuya's avatar
JINMEI Tatuya committed
438
                       "*");
439

440
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
441
442
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
443
    EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
444
445
    EXPECT_EQ(0, result);

446
    session.addMessage(el("{ \"command\": \"bad_command\" }"), "Spec29", "*");
447
    result = mccs.checkCommand();
448
    EXPECT_EQ(0, session.getMsgQueue()->size());
449
    EXPECT_EQ(0, result);
450

JINMEI Tatuya's avatar
JINMEI Tatuya committed
451
    session.addMessage(el("{ \"command\": [ \"bad_command\" ] }"),
452
                       "Spec29", "*");
453
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
454
455
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
456
    EXPECT_EQ("{ \"result\": [ 1, \"bad command\" ] }", msg->str());
457
458
    EXPECT_EQ(0, result);

459
460
    session.addMessage(el("{ \"command\": [ \"command_with_arg\", {\"number\": 1} ] }"),
                       "Spec29", "*");
461
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
462
463
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
464
    EXPECT_EQ("{ \"result\": [ 0, 2 ] }", msg->str());
465
466
    EXPECT_EQ(0, result);

467
    session.addMessage(el("{ \"command\": [ \"command_with_arg\" ] }"), "Spec29", "*");
468
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
469
470
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
471
    EXPECT_EQ("{ \"result\": [ 1, \"arg missing\" ] }", msg->str());
472
473
    EXPECT_EQ(0, result);

474
    session.addMessage(el("{ \"command\": [ \"command_with_arg\", \"asdf\" ] }"), "Spec29", "*");
475
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
476
477
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
478
    EXPECT_EQ("{ \"result\": [ 3, \"Error in command validation: args for command command_with_arg is not a map\" ] }", msg->str());
479
480
481
    EXPECT_EQ(0, result);

    mccs.setCommandHandler(NULL);
482
    session.addMessage(el("{ \"command\": [ \"whatever\" ] }"), "Spec29", "*");
483
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
484
485
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
486
    EXPECT_EQ("{ \"result\": [ 1, \"Command given but no command handler for module\" ] }", msg->str());
487
    EXPECT_EQ(0, result);
488
489
490
491
492
493
494
495
496
}

// A heuristic workaround for clang++: It doesn't seem to compile the whole
// test, probably due to its length.  Dividing the tests into two separate
// test cases seems to work.
TEST_F(CCSessionTest, checkCommand2) {
    session.getMessages()->add(createAnswer(0, el("{}")));
    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler,
497
                         my_command_handler, true, false);
498
499
500
501
502
503
    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
    ConstElementPtr msg;
    std::string group, to;
    // checked above, drop em
    msg = session.getFirstMessage(group, to);
    msg = session.getFirstMessage(group, to);
504
505

    EXPECT_EQ(1, mccs.getValue("item1")->intValue());
506
    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 2 } ] }"), "Spec29", "*");
507
    int result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
508
509
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
510
    EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
511
512
513
    EXPECT_EQ(0, result);
    EXPECT_EQ(2, mccs.getValue("item1")->intValue());

514
    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": \"asdf\" } ] }"), "Spec29", "*");
515
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
516
517
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
518
    EXPECT_EQ("{ \"result\": [ 2, \"Error in config validation: Type mismatch\" ] }", msg->str());
519
520
521
    EXPECT_EQ(0, result);
    EXPECT_EQ(2, mccs.getValue("item1")->intValue());

522
    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 5 } ] }"), "Spec29", "*");
523
    result = mccs.checkCommand();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
524
525
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
526
    EXPECT_EQ("{ \"result\": [ 6, \"I do not like the number 5\" ] }", msg->str());
527
528
529
    EXPECT_EQ(0, result);
    EXPECT_EQ(2, mccs.getValue("item1")->intValue());
}
530

531
532
533
534
535
std::string remote_module_name;
int remote_item1(0);
ConstElementPtr remote_config;
ModuleCCSession *remote_mccs(NULL);

Jelte Jansen's avatar
Jelte Jansen committed
536
537
538
void remoteHandler(const std::string& module_name,
                   ConstElementPtr config,
                   const ConfigData&) {
539
540
541
542
543
544
    remote_module_name = module_name;
    remote_item1 = remote_mccs->getRemoteConfigValue("Spec2", "item1")->
        intValue();
    remote_config = config;
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
545
TEST_F(CCSessionTest, remoteConfig) {
546
547
548
    std::string module_name;
    int item1;
    
549
550
    ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL,
                         false, false);
551
    EXPECT_TRUE(session.haveSubscription("Spec1", "*"));
552
553
554
    
    // first simply connect, with no config values, and see we get
    // the default
JINMEI Tatuya's avatar
JINMEI Tatuya committed
555
    session.getMessages()->add(createAnswer(0, el("{}")));
556

557
    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
558
559
    module_name = mccs.addRemoteConfig(ccspecfile("spec2.spec"));
    EXPECT_EQ("Spec2", module_name);
560
    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
561
562
563
564
565
566

    item1 = mccs.getRemoteConfigValue(module_name, "item1")->intValue();
    EXPECT_EQ(1, item1);

    // Remove it and see we get an error asking for a config value
    mccs.removeRemoteConfig(module_name);
567
    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
568
569
570
    EXPECT_THROW(mccs.getRemoteConfigValue(module_name, "item1"), CCSessionError);

    // Now re-add it, with a specific config value, and see we get that
JINMEI Tatuya's avatar
JINMEI Tatuya committed
571
    session.getMessages()->add(createAnswer(0, el("{ \"item1\": 2 }")));
572
573
574
575
576
    module_name = mccs.addRemoteConfig(ccspecfile("spec2.spec"));
    item1 = mccs.getRemoteConfigValue(module_name, "item1")->intValue();
    EXPECT_EQ(2, item1);

    // Try a config_update command
JINMEI Tatuya's avatar
JINMEI Tatuya committed
577
    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 3 } ] }"), module_name, "*");
578
579
580
581
582
583
    mccs.checkCommand();
    item1 = mccs.getRemoteConfigValue(module_name, "item1")->intValue();
    EXPECT_EQ(3, item1);

    // remove, re-add, now with a *bad* config request answer
    mccs.removeRemoteConfig(module_name);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
584
    session.getMessages()->add(el("{}"));
585
586
    EXPECT_THROW(mccs.addRemoteConfig(ccspecfile("spec2.spec")), CCSessionError);
    
JINMEI Tatuya's avatar
JINMEI Tatuya committed
587
    session.getMessages()->add(createAnswer(1, "my_error"));
588
589
    EXPECT_THROW(mccs.addRemoteConfig(ccspecfile("spec2.spec")), CCSessionError);
    
JINMEI Tatuya's avatar
JINMEI Tatuya committed
590
    session.getMessages()->add(createAnswer());
591
    EXPECT_THROW(mccs.addRemoteConfig(ccspecfile("spec2.spec")), CCSessionError);
592
593
594
595
596
597
598
599

    {
        SCOPED_TRACE("With module name");
        // Try adding it with downloading the spec from config manager
        ModuleSpec spec(moduleSpecFromFile(ccspecfile("spec2.spec")));
        session.getMessages()->add(createAnswer(0, spec.getFullSpec()));
        session.getMessages()->add(createAnswer(0, el("{}")));

600
601
        EXPECT_NO_THROW(module_name = mccs.addRemoteConfig("Spec2", NULL,
                                                           false));
602

603
        const size_t qsize(session.getMsgQueue()->size());
604
605
        EXPECT_TRUE(session.getMsgQueue()->get(qsize - 2)->equals(*el(
            "[ \"ConfigManager\", \"*\", { \"command\": ["
606
            "\"get_module_spec\", { \"module_name\": \"Spec2\" } ] }, -1 ]")));
607
608
        EXPECT_TRUE(session.getMsgQueue()->get(qsize - 1)->equals(*el(
            "[ \"ConfigManager\", \"*\", { \"command\": [ \"get_config\","
609
            "{ \"module_name\": \"Spec2\" } ] }, -1 ]")));
610
        EXPECT_EQ("Spec2", module_name);
611
612
        // Since we returned an empty local config above, the default value
        // for "item1", which is 1, should be used.
613
614
615
616
617
618
619
        EXPECT_NO_THROW(item1 =
                        mccs.getRemoteConfigValue(module_name,
                                                  "item1")->intValue());
        EXPECT_EQ(1, item1);

        mccs.removeRemoteConfig(module_name);
    }
620

621
622
623
624
625
626
627
628
629
630
631
632
    {
        SCOPED_TRACE("With bad module name");
        // It is almost the same as above, but we supply wrong module name.
        // It should fail.
        // Try adding it with downloading the spec from config manager
        ModuleSpec spec(moduleSpecFromFile(ccspecfile("spec2.spec")));
        session.getMessages()->add(createAnswer(0, spec.getFullSpec()));

        EXPECT_THROW(module_name = mccs.addRemoteConfig("Spec1", NULL, false),
                     CCSessionError);
    }

633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
    {
        // Try adding it with a handler.
        // Pass non-default value to see the handler is called after
        // downloading the configuration, not too soon.
        SCOPED_TRACE("With handler");
        session.getMessages()->add(createAnswer(0, el("{ \"item1\": 2 }")));
        remote_mccs = &mccs;
        module_name = mccs.addRemoteConfig(ccspecfile("spec2.spec"),
                                           remoteHandler);
        {
            SCOPED_TRACE("Before update");
            EXPECT_EQ("Spec2", module_name);
            EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
            // Now check the parameters the remote handler stored
            // This also checks it was called
            EXPECT_EQ("Spec2", remote_module_name);
            remote_module_name = "";
            EXPECT_EQ(2, remote_item1);
            remote_item1 = 0;
            if (remote_config) {
                EXPECT_EQ(2, remote_config->get("item1")->intValue());
            } else {
                ADD_FAILURE() << "Remote config not set";
            }
            remote_config.reset();
            // Make sure normal way still works
            item1 = mccs.getRemoteConfigValue(module_name,
                                              "item1")->intValue();
            EXPECT_EQ(2, item1);
        }

        {
            SCOPED_TRACE("After update");
            session.addMessage(el("{ \"command\": [ \"config_update\", "
                                  "{ \"item1\": 3 } ] }"), module_name, "*");
            mccs.checkCommand();
            EXPECT_EQ("Spec2", remote_module_name);
            remote_module_name = "";
            EXPECT_EQ(3, remote_item1);
            remote_item1 = 0;
            if (remote_config) {
                EXPECT_EQ(3, remote_config->get("item1")->intValue());
            } else {
                ADD_FAILURE() << "Remote config not set";
            }
            remote_config.reset();
            // Make sure normal way still works
            item1 = mccs.getRemoteConfigValue(module_name,
                                              "item1")->intValue();
            EXPECT_EQ(3, item1);
        }

        remote_mccs = NULL;
        mccs.removeRemoteConfig(module_name);

        {
            SCOPED_TRACE("When removed");
            // Make sure nothing is called any more
            session.addMessage(el("{ \"command\": [ \"config_update\", "
                                  "{ \"item1\": 4 } ] }"), module_name, "*");
            EXPECT_EQ("", remote_module_name);
            EXPECT_EQ(0, remote_item1);
            EXPECT_FALSE(remote_config);
        }
    }
698
699
}

700
TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
701
    // client will ask for config
702
    session.getMessages()->add(createAnswer(0, el("{  }")));
703

704
    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
705
    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler,
706
                         my_command_handler, false, false);
707
    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
708

709
    EXPECT_EQ(2, session.getMsgQueue()->size());
710
    ConstElementPtr msg;
711
712
    std::string group, to;
    // drop the module_spec and config commands
713
714
    session.getFirstMessage(group, to);
    session.getFirstMessage(group, to);
715

716
    session.getMessages()->add(createAnswer(0, el("{  }")));
717
    mccs.addRemoteConfig(ccspecfile("spec1.spec"));
718
719
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
720
721

    // Check if commands for the module are handled
722
    session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec29", "*");
723
    int result = mccs.checkCommand();
724
725
    EXPECT_EQ(1, session.getMsgQueue()->size());
    msg = session.getFirstMessage(group, to);
726
727
728
729
    EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
    EXPECT_EQ(0, result);

    // Check if commands for the other module are ignored
730
731
    session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec1", "*");
    EXPECT_EQ(1, session.getMsgQueue()->size());
732
    result = mccs.checkCommand();
733
    EXPECT_EQ(0, session.getMsgQueue()->size());
734
    EXPECT_EQ(0, result);
735
736
}

737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
TEST_F(CCSessionTest, initializationFail) {
    // bad specification
    EXPECT_THROW(ModuleCCSession(ccspecfile("spec8.spec"), session,
                                 NULL, NULL), CCSessionInitError);

    // file that does not exist
    EXPECT_THROW(ModuleCCSession(ccspecfile("does_not_exist_spec"),
                                 session, NULL, NULL),
                                 CCSessionInitError);


    session.getMessages()->add(createAnswer(1, el("\"just an error\"")));

    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
    EXPECT_THROW(ModuleCCSession(ccspecfile("spec29.spec"), session,
                                 my_config_handler, my_command_handler),
                                 CCSessionInitError);
    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
}

757
758
// Test it throws when we try to start it twice (once from the constructor)
TEST_F(CCSessionTest, doubleStartImplicit) {
759
760
    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, NULL, NULL,
                         true, false);
761
762
763
764
765
766
    EXPECT_THROW(mccs.start(), CCSessionError);
}

// The same, but both starts are explicit
TEST_F(CCSessionTest, doubleStartExplicit) {
    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, NULL, NULL,
767
                         false, false);
768
769
770
771
772
773
774
    mccs.start();
    EXPECT_THROW(mccs.start(), CCSessionError);
}

// Test we can request synchronous receive before we start the session,
// and check there's the mechanism if we do it after
TEST_F(CCSessionTest, delayedStart) {
775
776
    ModuleCCSession mccs(ccspecfile("spec2.spec"), session, NULL, NULL,
                         false, false);
777
778
779
780
781
782
783
784
785
    session.getMessages()->add(createAnswer());
    ConstElementPtr env, answer;
    EXPECT_NO_THROW(session.group_recvmsg(env, answer, false, 3));
    mccs.start();
    session.getMessages()->add(createAnswer());
    EXPECT_THROW(session.group_recvmsg(env, answer, false, 3),
                 FakeSession::DoubleRead);
}

786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
TEST_F(CCSessionTest, loggingStart) {
    // provide the logging module spec
    ConstElementPtr log_spec = moduleSpecFromFile(LOG_SPEC_FILE).getFullSpec();
    session.getMessages()->add(createAnswer(0, log_spec));
    // just give an empty config
    session.getMessages()->add(createAnswer(0, el("{}")));
    ModuleCCSession mccs(ccspecfile("spec2.spec"), session, NULL, NULL,
                         true, true);
    EXPECT_TRUE(session.haveSubscription("Logging", "*"));
}

TEST_F(CCSessionTest, loggingStartBadSpec) {
    // provide the logging module spec
    session.getMessages()->add(createAnswer(0, el("{}")));
    // just give an empty config
    session.getMessages()->add(createAnswer(0, el("{}")));
    EXPECT_THROW(new ModuleCCSession(ccspecfile("spec2.spec"), session,
803
                 NULL, NULL), ModuleSpecError);
804
805
806
    EXPECT_FALSE(session.haveSubscription("Logging", "*"));
}

807
808
809
810
811
// Similar to the above, but more implicitly by calling addRemoteConfig().
// We should construct ModuleCCSession with start_immediately being false
// if we need to call addRemoteConfig().
// The correct cases are covered in remoteConfig test.
TEST_F(CCSessionTest, doubleStartWithAddRemoteConfig) {
812
813
    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, NULL, NULL,
                         true, false);
814
815
816
817
    session.getMessages()->add(createAnswer(0, el("{}")));
    EXPECT_THROW(mccs.addRemoteConfig(ccspecfile("spec2.spec")),
                 FakeSession::DoubleRead);
}
818

819
820
/// \brief Test fixture for asynchronous receiving of messages.
///
Jelte Jansen's avatar
Jelte Jansen committed
821
/// This is an extension to the CCSessionTest. It would be possible to add
822
823
824
825
826
827
828
/// the functionality to the CCSessionTest, but it is going to be used
/// only by few tests and is non-trivial, so it is placed to a separate
/// sub-class.
class AsyncReceiveCCSessionTest : public CCSessionTest {
protected:
    AsyncReceiveCCSessionTest() :
        mccs_(ccspecfile("spec29.spec"), session, NULL, NULL, false, false),
829
        msg_(el("{\"result\": [0]}")),
830
        next_flag_(0)
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
831
832
833
834
    {
        // This is just to make sure the messages get through the fake
        // session.
        session.subscribe("test group");
835
        session.subscribe("other group");
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
836
        session.subscribe("<ignored>");
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
837
838
839
840
        // Get rid of all unrelated stray messages
        while (session.getMsgQueue()->size() > 0) {
            session.getMsgQueue()->remove(0);
        }
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
841
    }
842
843
844
845
846
847
    /// \brief Convenience function to queue a request to get a command
    ///     message.
    ModuleCCSession::AsyncRecvRequestID
        registerCommand(const string& recipient)
    {
        return (mccs_.groupRecvMsgAsync(
848
849
850
                    boost::bind(&AsyncReceiveCCSessionTest::callback, this,
                                next_flag_++, _1, _2, _3), false, -1,
                    recipient));
851
852
853
854
855
856
857
    }
    /// \brief Convenience function to queue a request to get a reply
    ///     message.
    ModuleCCSession::AsyncRecvRequestID
        registerReply(int seq)
    {
        return (mccs_.groupRecvMsgAsync(
858
859
                    boost::bind(&AsyncReceiveCCSessionTest::callback, this,
                                next_flag_++, _1, _2, _3), true, seq));
860
861
862
863
864
865
866
867
868
869
870
    }
    /// \brief Check the next called callback was with this flag
    void called(int flag) {
        ASSERT_FALSE(called_.empty());
        EXPECT_EQ(flag, *called_.begin());
        called_.pop_front();
    }
    /// \brief Checks that no more callbacks were called.
    void nothingCalled() {
        EXPECT_TRUE(called_.empty());
    }
Jelte Jansen's avatar
Jelte Jansen committed
871
    /// \brief The tested session.
872
873
874
    ModuleCCSession mccs_;
    /// \brief The value of message on the last called callback.
    ConstElementPtr last_msg_;
875
876
    /// \brief A message that can be used
    ConstElementPtr msg_;
877
878
879
    // Shared part of the simpleCommand and similar tests.
    void commandTest(const string& group) {
        // Push the message inside
880
        session.addMessage(msg_, "test group", "<unused>");
881
882
883
884
885
886
887
888
889
        EXPECT_TRUE(mccs_.hasQueuedMsgs());
        // Register the callback
        registerCommand(group);
        // But the callback should not be called yet
        // (even if the message is there).
        nothingCalled();
        // But when we call the checkCommand(), it should be called.
        mccs_.checkCommand();
        called(0);
890
        EXPECT_EQ(msg_, last_msg_);
891
892
893
894
895
896
        // But only once
        nothingCalled();
        // And the message should be eaten
        EXPECT_FALSE(mccs_.hasQueuedMsgs());
        // The callback should have been eaten as well, inserting another
        // message will not invoke it again
897
        session.addMessage(msg_, "test group", "<unused>");
898
899
900
901
902
903
        mccs_.checkCommand();
        nothingCalled();
    }
    /// \brief Shared part of the simpleResponse and wildcardResponse tests.
    void responseTest(int seq) {
        // Push the message inside
904
        session.addMessage(msg_, "<ignored>", "<unused>", 1);
905
906
907
908
909
910
911
912
913
        EXPECT_TRUE(mccs_.hasQueuedMsgs());
        // Register the callback
        registerReply(seq);
        // But the callback should not be called yet
        // (even if the message is there).
        nothingCalled();
        // But when we call the checkCommand(), it should be called.
        mccs_.checkCommand();
        called(0);
914
        EXPECT_EQ(msg_, last_msg_);
915
916
917
918
919
920
        // But only once
        nothingCalled();
        // And the message should be eaten
        EXPECT_FALSE(mccs_.hasQueuedMsgs());
        // The callback should have been eaten as well, inserting another
        // message will not invoke it again
921
        session.addMessage(msg_, "test group", "<unused>");
922
923
924
925
926
927
        mccs_.checkCommand();
        nothingCalled();
    }
    /// \brief Shared part of the noMatch* tests
    void noMatchTest(int seq, int wanted_seq, bool is_reply) {
        // Push the message inside
928
        session.addMessage(msg_, "other group", "<unused>", seq);
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
        EXPECT_TRUE(mccs_.hasQueuedMsgs());
        // Register the callback
        if (is_reply) {
            registerReply(wanted_seq);
        } else {
            registerCommand("test group");
        }
        // But the callback should not be called yet
        // (even if the message is there).
        nothingCalled();
        // And even not now, because it does not match.
        mccs_.checkCommand();
        nothingCalled();
        // And the message should be eaten by the checkCommand
        EXPECT_FALSE(mccs_.hasQueuedMsgs());
    }
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
private:
    /// \brief The next flag to be handed out
    int next_flag_;
    /// \brief Flags of callbacks already called (as FIFO)
    list<int> called_;
    /// \brief This is the callback registered to the tested groupRecvMsgAsync
    ///     function.
    void callback(int store_flag, const ConstElementPtr&,
                  const ConstElementPtr& msg,
                  const ModuleCCSession::AsyncRecvRequestID&)
    {
        called_.push_back(store_flag);
        last_msg_ = msg;
    }
};

// Test we can receive a command, without anything fancy yet
TEST_F(AsyncReceiveCCSessionTest, simpleCommand) {
963
964
965
966
967
968
969
    commandTest("test group");
}

// Test we can receive a "wildcard" command - without specifying the
// group to subscribe to. Very similar to simpleCommand test.
TEST_F(AsyncReceiveCCSessionTest, wildcardCommand) {
    commandTest("");
970
971
972
973
}

// Very similar to simpleCommand, but with a response message
TEST_F(AsyncReceiveCCSessionTest, simpleResponse) {
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
    responseTest(1);
}

// Matching a response message with wildcard
TEST_F(AsyncReceiveCCSessionTest, wildcardResponse) {
    responseTest(-1);
}

// Check that a wrong command message is not matched
TEST_F(AsyncReceiveCCSessionTest, noMatchCommand) {
    noMatchTest(-1, -1, false);
}

// Check that a wrong response message is not matched
TEST_F(AsyncReceiveCCSessionTest, noMatchResponse) {
    noMatchTest(2, 3, true);
}

// Check that a command will not match on a reply check and vice versa
TEST_F(AsyncReceiveCCSessionTest, noMatchResponseAgainstCommand) {
    // Send a command and check it is not matched as a response
    noMatchTest(-1, -1, true);
}

TEST_F(AsyncReceiveCCSessionTest, noMatchCommandAgainstResponse) {
    noMatchTest(2, -1, false);
1000
1001
}

1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
// We check for command several times before the message actually arrives.
TEST_F(AsyncReceiveCCSessionTest, delayedCallback) {
    // First, register the callback
    registerReply(1);
    // And see it is not called, because the message is not there yet
    EXPECT_FALSE(mccs_.hasQueuedMsgs());
    for (size_t i(0); i < 100; ++ i) {
        mccs_.checkCommand();
        EXPECT_FALSE(mccs_.hasQueuedMsgs());
        nothingCalled();
    }
    // Now the message finally arrives
    session.addMessage(msg_, "<ignored>", "<unused>", 1);
    EXPECT_TRUE(mccs_.hasQueuedMsgs());
    // And now, the callback is happily triggered.
    mccs_.checkCommand();
    called(0);
    EXPECT_EQ(msg_, last_msg_);
    // But only once
    nothingCalled();
}

// See that if we put multiple messages inside, and request some callbacks,
// the callbacks are called in the order of messages, not in the order they
// were registered.
TEST_F(AsyncReceiveCCSessionTest, outOfOrder) {
    // First, put some messages there
    session.addMessage(msg_, "<ignored>", "<unused>", 1);
    session.addMessage(msg_, "test group", "<unused>");
    session.addMessage(msg_, "other group", "<unused>");
    session.addMessage(msg_, "<ignored>", "<unused>", 2);
    session.addMessage(msg_, "<ignored>", "<unused>", 3);
    session.addMessage(msg_, "<ignored>", "<unused>", 4);
    // Now register some callbacks
    registerReply(13); // Will not be called
    registerCommand("other group"); // Matches 3rd message
    registerReply(2); // Matches 4th message
    registerCommand(""); // Matches the 2nd message
    registerCommand("test group"); // Will not be called
    registerReply(-1); // Matches the 1st message
    registerReply(-1); // Matches the 5th message
    // Process all messages there
    while (mccs_.hasQueuedMsgs()) {
        mccs_.checkCommand();
    }
    // These are the numbers of callbacks in the order of messages
    called(5);
    called(3);
    called(1);
    called(2);
    called(6);
    // The last message doesn't trigger anything, so nothing more is called
    nothingCalled();
}

1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
// We first add, then remove the callback again and check that nothing is
// matched.
TEST_F(AsyncReceiveCCSessionTest, cancel) {
    // Add the callback
    ModuleCCSession::AsyncRecvRequestID request(registerReply(1));
    // Add corresponding message
    session.addMessage(msg_, "<ignored>", "<unused>", 1);
    EXPECT_TRUE(mccs_.hasQueuedMsgs());
    // And now, remove the callback again
    mccs_.cancelAsyncRecv(request);
    // And see that Nothing Happens(TM)
    mccs_.checkCommand();
    EXPECT_FALSE(mccs_.hasQueuedMsgs());
    nothingCalled();
}

// We add multiple requests and cancel only one of them to see the rest
// is unaffected.
TEST_F(AsyncReceiveCCSessionTest, cancelSome) {
    // Register few callbacks
    registerReply(1);
    ModuleCCSession::AsyncRecvRequestID request(registerCommand(""));
    registerCommand("test group");
    // Put some messages there
    session.addMessage(msg_, "test group", "<unused>");
    session.addMessage(msg_, "<ignored>", "<unused>", 1);
    // Cancel the second callback. Therefore the first message will be matched
    // by the third callback, not by the second.
    mccs_.cancelAsyncRecv(request);
    // Now, process the messages
    mccs_.checkCommand();
    mccs_.checkCommand();
    // And see how they matched
    called(2);
    called(0);
    nothingCalled();
}

1095
1096
1097
1098
1099
1100
1101
1102
1103
void doRelatedLoggersTest(const char* input, const char* expected) {
    ConstElementPtr all_conf = isc::data::Element::fromJSON(input);
    ConstElementPtr expected_conf = isc::data::Element::fromJSON(expected);
    EXPECT_EQ(*expected_conf, *isc::config::getRelatedLoggers(all_conf));
}

TEST(LogConfigTest, relatedLoggersTest) {
    // make sure logger configs for 'other' programs are ignored,
    // and that * is substituted correctly
1104
1105
1106
    // We'll use a root logger name of "b10-test".
    isc::log::setRootLoggerName("b10-test");

1107
1108
    doRelatedLoggersTest("[{ \"name\": \"other_module\" }]",
                         "[]");
1109
1110
    doRelatedLoggersTest("[{ \"name\": \"other_module.somelib\" }]",
                         "[]");
1111
    doRelatedLoggersTest("[{ \"name\": \"