dhcp_parsers_unittest.cc 84.1 KB
Newer Older
1
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
2
//
3
4
5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7

#include <config.h>
8
#include <cc/command_interpreter.h>
9
#include <cc/data.h>
10
11
12
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
13
#include <dhcp/option6_addrlst.h>
14
#include <dhcp/tests/iface_mgr_test_config.h>
15
#include <dhcpsrv/cfgmgr.h>
16
#include <dhcpsrv/subnet.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
17
#include <dhcpsrv/cfg_mac_source.h>
18
#include <dhcpsrv/parsers/dhcp_parsers.h>
Stephen Morris's avatar
Stephen Morris committed
19
#include <dhcpsrv/tests/test_libraries.h>
20
#include <dhcpsrv/testutils/config_result_check.h>
21
#include <exceptions/exceptions.h>
22
#include <hooks/hooks_manager.h>
23
24
25

#include <gtest/gtest.h>
#include <boost/foreach.hpp>
26
#include <boost/pointer_cast.hpp>
27
#include <boost/scoped_ptr.hpp>
28
29
30
31
32
33
34

#include <map>
#include <string>

using namespace std;
using namespace isc;
using namespace isc::config;
35
36
using namespace isc::data;
using namespace isc::dhcp;
37
using namespace isc::dhcp::test;
38
using namespace isc::hooks;
39
40
41
42
43
44
45
46

namespace {

/// @brief DHCP Parser test fixture class
class DhcpParserTest : public ::testing::Test {
public:
    /// @brief Constructor
    DhcpParserTest() {
47
48
49
50
51
52
53
54
55
56
        resetIfaceCfg();
    }

    /// @brief Destructor.
    virtual ~DhcpParserTest() {
        resetIfaceCfg();
    }

    /// @brief Resets selection of the interfaces from previous tests.
    void resetIfaceCfg() {
57
        CfgMgr::instance().clear();
58
59
60
61
62
    }
};


/// @brief Check BooleanParser basic functionality.
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
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Rejects a non-boolean element.
/// 3. Builds with a valid true value.
/// 4. Bbuils with a valid false value.
/// 5. Updates storage upon commit.
TEST_F(DhcpParserTest, booleanParserTest) {

    const std::string name = "boolParm";

    // Verify that parser does not allow empty for storage.
    BooleanStoragePtr bs;
    EXPECT_THROW(BooleanParser(name, bs), isc::dhcp::DhcpConfigError);

    // Construct parser for testing.
    BooleanStoragePtr storage(new BooleanStorage());
    BooleanParser parser(name, storage);

    // Verify that parser with rejects a non-boolean element.
    ElementPtr wrong_element = Element::create("I am a string");
    EXPECT_THROW(parser.build(wrong_element), isc::BadValue);

    // Verify that parser will build with a valid true value.
    bool test_value = true;
    ElementPtr element = Element::create(test_value);
    ASSERT_NO_THROW(parser.build(element));

    // Verify that commit updates storage.
92
    bool actual_value = !test_value;
93
94
95
96
97
98
99
100
101
102
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);

    // Verify that parser will build with a valid false value.
    test_value = false;
    element->setValue(test_value);
    EXPECT_NO_THROW(parser.build(element));

    // Verify that commit updates storage.
103
    actual_value = !test_value;
104
105
106
107
108
109
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);
}

/// @brief Check StringParser basic functionality
110
///
111
112
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
113
/// 2. Builds with a nont string value.
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/// 3. Builds with a string value.
/// 4. Updates storage upon commit.
TEST_F(DhcpParserTest, stringParserTest) {

    const std::string name = "strParm";

    // Verify that parser does not allow empty for storage.
    StringStoragePtr bs;
    EXPECT_THROW(StringParser(name, bs), isc::dhcp::DhcpConfigError);

    // Construct parser for testing.
    StringStoragePtr storage(new StringStorage());
    StringParser parser(name, storage);

128
129
130
    // Verify that parser with accepts a non-string element.
    ElementPtr element = Element::create(9999);
    EXPECT_NO_THROW(parser.build(element));
131

132
133
134
135
136
137
    // Verify that commit updates storage.
    parser.commit();
    std::string actual_value;
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ("9999", actual_value);

138
139
    // Verify that parser will build with a string value.
    const std::string test_value = "test value";
140
    element = Element::create(test_value);
141
142
143
144
145
146
147
148
149
    ASSERT_NO_THROW(parser.build(element));

    // Verify that commit updates storage.
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);
}

/// @brief Check Uint32Parser basic functionality
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
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Rejects a non-integer element.
/// 3. Rejects a negative value.
/// 4. Rejects too large a value.
/// 5. Builds with value of zero.
/// 6. Builds with a value greater than zero.
/// 7. Updates storage upon commit.
TEST_F(DhcpParserTest, uint32ParserTest) {

    const std::string name = "intParm";

    // Verify that parser does not allow empty for storage.
    Uint32StoragePtr bs;
    EXPECT_THROW(Uint32Parser(name, bs), isc::dhcp::DhcpConfigError);

    // Construct parser for testing.
    Uint32StoragePtr storage(new Uint32Storage());
    Uint32Parser parser(name, storage);

    // Verify that parser with rejects a non-interger element.
    ElementPtr wrong_element = Element::create("I am a string");
    EXPECT_THROW(parser.build(wrong_element), isc::BadValue);

    // Verify that parser with rejects a negative value.
    ElementPtr int_element = Element::create(-1);
    EXPECT_THROW(parser.build(int_element), isc::BadValue);

179
180
    // Verify that parser with rejects too large a value provided we are on
    // 64-bit platform.
181
182
183
184
185
    if (sizeof(long) > sizeof(uint32_t)) {
        long max = (long)(std::numeric_limits<uint32_t>::max()) + 1;
        int_element->setValue(max);
        EXPECT_THROW(parser.build(int_element), isc::BadValue);
    }
186
187
188
189
190
191

    // Verify that parser will build with value of zero.
    int test_value = 0;
    int_element->setValue((long)test_value);
    ASSERT_NO_THROW(parser.build(int_element));

192
193
194
195
196
197
    // Verify that commit updates storage.
    parser.commit();
    uint32_t actual_value = 0;
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);

198
199
200
201
202
203
204
205
206
207
208
    // Verify that parser will build with a valid positive value.
    test_value = 77;
    int_element->setValue((long)test_value);
    ASSERT_NO_THROW(parser.build(int_element));

    // Verify that commit updates storage.
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);
}

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/// @brief Check MACSourcesListConfigParser  basic functionality
///
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Does not allow name other than "mac-sources"
/// 3. Parses list of mac sources and adds them to CfgMgr
TEST_F(DhcpParserTest, MacSourcesListConfigParserTest) {

    const std::string valid_name = "mac-sources";
    const std::string bogus_name = "bogus-name";

    ParserContextPtr parser_context(new ParserContext(Option::V6));

    // Verify that parser constructor fails if parameter name isn't "mac-sources"
    EXPECT_THROW(MACSourcesListConfigParser(bogus_name, parser_context),
                 isc::BadValue);

    // That's an equivalent of the following snippet:
    // "mac-sources: [ \"duid\", \"ipv6\" ]";
    ElementPtr config = Element::createList();
    config->add(Element::create("duid"));
    config->add(Element::create("ipv6-link-local"));

    boost::scoped_ptr<MACSourcesListConfigParser>
        parser(new MACSourcesListConfigParser(valid_name, parser_context));

    // This should parse the configuration and add eth0 and eth1 to the list
    // of interfaces that server should listen on.
    EXPECT_NO_THROW(parser->build(config));
    EXPECT_NO_THROW(parser->commit());

    // Use CfgMgr instance to check if eth0 and eth1 was added, and that
    // eth2 was not added.
    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
    ASSERT_TRUE(cfg);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
244
    CfgMACSources configured_sources =  cfg->getMACSources().get();
245
246

    ASSERT_EQ(2, configured_sources.size());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
247
248
    EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]);
    EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]);
249
250
}

251
/// @brief Test Fixture class which provides basic structure for testing
252
253
254
255
256
257
258
/// configuration parsing.  This is essentially the same structure provided
/// by dhcp servers.
class ParseConfigTest : public ::testing::Test {
public:
    /// @brief Constructor
    ParseConfigTest() {
        reset_context();
259
        CfgMgr::instance().clear();
260
261
    }

262
263
    ~ParseConfigTest() {
        reset_context();
264
        CfgMgr::instance().clear();
265
266
    }

267
    /// @brief Parses a configuration.
268
269
    ///
    /// Parse the given configuration, populating the context storage with
270
271
    /// the parsed elements.
    ///
272
273
274
    /// @param config_set is the set of elements to parse.
    /// @return returns an ConstElementPtr containing the numeric result
    /// code and outcome comment.
275
    isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
276
277
278
279
280
281
282
283
284
285
286
287
288
289
                                           config_set) {
        // Answer will hold the result.
        ConstElementPtr answer;
        if (!config_set) {
            answer = isc::config::createAnswer(1,
                                 string("Can't parse NULL config"));
            return (answer);
        }

        // option parsing must be done last, so save it if we hit if first
        ParserPtr option_parser;

        ConfigPair config_pair;
        try {
290
            // Iterate over the config elements.
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
            const std::map<std::string, ConstElementPtr>& values_map =
                                                      config_set->mapValue();
            BOOST_FOREACH(config_pair, values_map) {
                // Create the parser based on element name.
                ParserPtr parser(createConfigParser(config_pair.first));
                // Options must be parsed last
                if (config_pair.first == "option-data") {
                    option_parser = parser;
                } else {
                    // Anything else  we can call build straight away.
                    parser->build(config_pair.second);
                    parser->commit();
                }
            }

            // The option values parser is the next one to be run.
307
            std::map<std::string, ConstElementPtr>::const_iterator
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
                                option_config = values_map.find("option-data");
            if (option_config != values_map.end()) {
                option_parser->build(option_config->second);
                option_parser->commit();
            }

            // Everything was fine. Configuration is successful.
            answer = isc::config::createAnswer(0, "Configuration committed.");
        } catch (const isc::Exception& ex) {
            answer = isc::config::createAnswer(1,
                        string("Configuration parsing failed: ") + ex.what());

        } catch (...) {
            answer = isc::config::createAnswer(1,
                                        string("Configuration parsing failed"));
        }

        return (answer);
    }

    /// @brief Create an element parser based on the element name.
    ///
330
331
332
333
334
    /// Creates a parser for the appropriate element and stores a pointer to it
    /// in the appropriate class variable.
    ///
    /// Note that the method currently it only supports option-defs, option-data
    /// and hooks-libraries.
335
336
    ///
    /// @param config_id is the name of the configuration element.
337
338
339
    ///
    /// @return returns a shared pointer to DhcpConfigParser.
    ///
340
    /// @throw throws NotImplemented if element name isn't supported.
341
342
    ParserPtr createConfigParser(const std::string& config_id) {
        ParserPtr parser;
343
344
        int family = parser_context_->universe_ == Option::V4 ?
            AF_INET : AF_INET6;
345
        if (config_id.compare("option-data") == 0) {
346
            parser.reset(new OptionDataListParser(config_id, CfgOptionPtr(),
347
                                                  family));
348

349
        } else if (config_id.compare("option-def") == 0) {
350
            parser.reset(new OptionDefListParser(config_id,
351
                                                 parser_context_));
352
353
354
355
356

        } else if (config_id.compare("hooks-libraries") == 0) {
            parser.reset(new HooksLibrariesParser(config_id));
            hooks_libraries_parser_ =
                boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
357
358
        } else if (config_id.compare("dhcp-ddns") == 0) {
            parser.reset(new D2ClientConfigParser(config_id));
359
360
361
362
363
364
365
366
367
        } else {
            isc_throw(NotImplemented,
                "Parser error: configuration parameter not supported: "
                << config_id);
        }

        return (parser);
    }

368
369
    /// @brief Convenience method for parsing a configuration
    ///
370
    /// Given a configuration string, convert it into Elements
371
    /// and parse them.
372
373
    /// @param config is the configuration string to parse
    ///
374
    /// @return retuns 0 if the configuration parsed successfully,
375
    /// non-zero otherwise failure.
376
    int parseConfiguration(const std::string& config) {
377
378
379
380
381
382
383
        int rcode_ = 1;
        // Turn config into elements.
        // Test json just to make sure its valid.
        ElementPtr json = Element::fromJSON(config);
        EXPECT_TRUE(json);
        if (json) {
            ConstElementPtr status = parseElementSet(json);
384
385
            ConstElementPtr comment = parseAnswer(rcode_, status);
            error_text_ = comment->stringValue();
386
387
388
389
390
            // If error was reported, the error string should contain
            // position of the data element which caused failure.
            if (rcode_ != 0) {
                EXPECT_TRUE(errorContainsPosition(status, "<string>"));
            }
391
392
393
394
395
        }

        return (rcode_);
    }

396
    /// @brief Find an option for a given space and code within the parser
397
398
399
400
401
    /// context.
    /// @param space is the space name of the desired option.
    /// @param code is the numeric "type" of the desired option.
    /// @return returns an OptionPtr which points to the found
    /// option or is empty.
402
    /// ASSERT_ tests don't work inside functions that return values
403
404
405
    OptionPtr getOptionPtr(std::string space, uint32_t code)
    {
        OptionPtr option_ptr;
406
407
        OptionContainerPtr options = CfgMgr::instance().getStagingCfg()->
            getCfgOption()->getAll(space);
408
409
410
411
        // Should always be able to get options list even if it is empty.
        EXPECT_TRUE(options);
        if (options) {
            // Attempt to find desired option.
412
            const OptionContainerTypeIndex& idx = options->get<1>();
413
            const OptionContainerTypeRange& range = idx.equal_range(code);
414
415
416
            int cnt = std::distance(range.first, range.second);
            EXPECT_EQ(1, cnt);
            if (cnt == 1) {
417
                OptionDescriptor desc = *(idx.begin());
418
                option_ptr = desc.option_;
419
420
421
422
                EXPECT_TRUE(option_ptr);
            }
        }

423
        return (option_ptr);
424
425
    }

426
    /// @brief Wipes the contents of the context to allowing another parsing
427
428
429
    /// during a given test if needed.
    void reset_context(){
        // Note set context universe to V6 as it has to be something.
430
        CfgMgr::instance().clear();
431
        parser_context_.reset(new ParserContext(Option::V6));
432
433
434

        // Ensure no hooks libraries are loaded.
        HooksManager::unloadLibraries();
435
436
437
438

        // Set it to minimal, disabled config
        D2ClientConfigPtr tmp(new D2ClientConfig());
        CfgMgr::instance().setD2ClientConfig(tmp);
439
440
    }

441
442
443
444
445
    /// @brief Parsers used in the parsing of the configuration
    ///
    /// Allows the tests to interrogate the state of the parsers (if required).
    boost::shared_ptr<HooksLibrariesParser> hooks_libraries_parser_;

446
447
    /// @brief Parser context - provides storage for options and definitions
    ParserContextPtr parser_context_;
448
449
450

    /// @brief Error string if the parsing failed
    std::string error_text_;
451
452
};

453
/// @brief Check basic parsing of option definitions.
454
///
455
/// Note that this tests basic operation of the OptionDefinitionListParser and
456
/// OptionDefinitionParser.  It uses a simple configuration consisting of
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
/// one definition and verifies that it is parsed and committed to storage
/// correctly.
TEST_F(ParseConfigTest, basicOptionDefTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 100,"
        "      \"type\": \"ipv4-address\","
        "      \"array\": False,"
        "      \"record-types\": \"\","
        "      \"space\": \"isc\","
        "      \"encapsulate\": \"\""
        "  } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
476
    ASSERT_EQ(0, rcode);
477

478

479
    // Verify that the option definition can be retrieved.
480
    OptionDefinitionPtr def =
481
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
482
483
484
485
486
487
488
489
    ASSERT_TRUE(def);

    // Verify that the option definition is correct.
    EXPECT_EQ("foo", def->getName());
    EXPECT_EQ(100, def->getCode());
    EXPECT_FALSE(def->getArrayType());
    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
490
491
492
493
494
495
496
497
498

    // Check if libdhcp++ runtime options have been updated.
    OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100);
    ASSERT_TRUE(def_libdhcp);

    // The LibDHCP should return a separate instance of the option definition
    // but the values should be equal.
    EXPECT_TRUE(def_libdhcp != def);
    EXPECT_TRUE(*def_libdhcp == *def);
499
500
}

501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
/// @brief Check minimal parsing of option definitions.
///
/// Same than basic but without optional parameters set to their default.
TEST_F(ParseConfigTest, minimalOptionDefTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 100,"
        "      \"type\": \"ipv4-address\","
        "      \"space\": \"isc\""
        "  } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
518
    ASSERT_EQ(0, rcode);
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533


    // Verify that the option definition can be retrieved.
    OptionDefinitionPtr def =
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
    ASSERT_TRUE(def);

    // Verify that the option definition is correct.
    EXPECT_EQ("foo", def->getName());
    EXPECT_EQ(100, def->getCode());
    EXPECT_FALSE(def->getArrayType());
    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
}

534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
/// @brief Check parsing of option definitions using default dhcp6 space.
///
/// Same than minimal but using the fact the default universe is V6
/// so the default space is dhcp6
TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 10000,"
        "      \"type\": \"ipv6-address\""
        "  } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
551
    ASSERT_EQ(0, rcode);
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566


    // Verify that the option definition can be retrieved.
    OptionDefinitionPtr def =
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("dhcp6", 10000);
    ASSERT_TRUE(def);

    // Verify that the option definition is correct.
    EXPECT_EQ("foo", def->getName());
    EXPECT_EQ(10000, def->getCode());
    EXPECT_FALSE(def->getArrayType());
    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
}

567
/// @brief Check basic parsing of options.
568
///
569
570
571
572
573
574
575
576
577
578
579
580
/// Note that this tests basic operation of the OptionDataListParser and
/// OptionDataParser.  It uses a simple configuration consisting of one
/// one definition and matching option data.  It verifies that the option
/// is parsed and committed to storage correctly.
TEST_F(ParseConfigTest, basicOptionDataTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 100,"
        "      \"type\": \"ipv4-address\","
581
        "      \"space\": \"isc\""
582
583
584
585
586
        " } ], "
        " \"option-data\": [ {"
        "    \"name\": \"foo\","
        "    \"space\": \"isc\","
        "    \"code\": 100,"
587
        "    \"data\": \"192.0.2.0\","
588
589
590
591
592
593
        "    \"csv-format\": True"
        " } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
594
    ASSERT_EQ(0, rcode);
595
596
597
598
599

    // Verify that the option can be retrieved.
    OptionPtr opt_ptr = getOptionPtr("isc", 100);
    ASSERT_TRUE(opt_ptr);

600
    // Verify that the option data is correct.
601
    std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
602
603
604
605

    EXPECT_EQ(val, opt_ptr->toText());
}

606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
/// @brief Check minimal parsing of options.
///
/// Same than basic but without optional parameters set to their default.
TEST_F(ParseConfigTest, minimalOptionDataTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 100,"
        "      \"type\": \"ipv4-address\","
        "      \"space\": \"isc\""
        " } ], "
        " \"option-data\": [ {"
        "    \"name\": \"foo\","
        "    \"space\": \"isc\","
        "    \"data\": \"192.0.2.0\""
        " } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
628
    ASSERT_EQ(0, rcode);
629
630
631
632
633

    // Verify that the option can be retrieved.
    OptionPtr opt_ptr = getOptionPtr("isc", 100);
    ASSERT_TRUE(opt_ptr);

634
    // Verify that the option data is correct.
635
636
637
638
639
    std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";

    EXPECT_EQ(val, opt_ptr->toText());
}

640
641
642
// This test checks behavior of the configuration parser for option data
// for different values of csv-format parameter and when there is an option
// definition present.
643
TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
644
645
646
647
648
649
650
651
652
    std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
        "    \"data\": \"192.0.2.0\""
        " } ]"
        "}";

653
654
655
    // The default universe is V6. We need to change it to use dhcp4 option
    // space.
    parser_context_->universe_ = Option::V4;
656
657
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
658
    ASSERT_EQ(0, rcode);
659

660
    // Verify that the option data is correct.
661
    OptionCustomPtr addr_opt = boost::dynamic_pointer_cast<
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
698
699
700
701
702
703
704
705
706
707
708
709
        OptionCustom>(getOptionPtr("dhcp4", 16));
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());

    // Explicitly enable csv-format.
    CfgMgr::instance().clear();
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
        "    \"csv-format\": True,"
        "    \"data\": \"192.0.2.0\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    ASSERT_EQ(0, rcode);

    // Verify that the option data is correct.
    addr_opt = boost::dynamic_pointer_cast<
        OptionCustom>(getOptionPtr("dhcp4", 16));
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());

    // Explicitly disable csv-format and use hex instead.
    CfgMgr::instance().clear();
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
        "    \"csv-format\": False,"
        "    \"data\": \"C0000200\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    ASSERT_EQ(0, rcode);

    // Verify that the option data is correct.
    addr_opt = boost::dynamic_pointer_cast<
        OptionCustom>(getOptionPtr("dhcp4", 16));
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
}

// This test checks behavior of the configuration parser for option data
// for different values of csv-format parameter and when there is no
// option definition.
710
TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
    // This option doesn't have any definition. It is ok to use such
    // an option but the data should be specified in hex, not as CSV.
    // Note that the parser will by default use the CSV format for the
    // data but only in case there is a suitable option definition.
    std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
        "    \"data\": \"1, 2, 5\""
        " } ]"
        "}";
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_NE(0, rcode);

    CfgMgr::instance().clear();
    // The data specified here will work both for CSV format and hex format.
    // What we want to test here is that when the csv-format is enforced, the
    // parser will fail because of lack of an option definition.
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
        "    \"csv-format\": True,"
        "    \"data\": \"0\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_NE(0, rcode);

    CfgMgr::instance().clear();
    // The same test case as above, but for the data specified in hex should
    // be successful.
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
        "    \"csv-format\": False,"
        "    \"data\": \"0\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    ASSERT_EQ(0, rcode);
    OptionPtr opt = getOptionPtr("dhcp6", 25000);
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getData().size());
    EXPECT_EQ(0, opt->getData()[0]);

    CfgMgr::instance().clear();
    // When csv-format is not specified, the parser will check if the definition
    // exists or not. Since there is no definition, the parser will accept the
    // data in hex.
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
        "    \"data\": \"123456\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    opt = getOptionPtr("dhcp6", 25000);
    ASSERT_TRUE(opt);
    ASSERT_EQ(3, opt->getData().size());
    EXPECT_EQ(0x12, opt->getData()[0]);
    EXPECT_EQ(0x34, opt->getData()[1]);
    EXPECT_EQ(0x56, opt->getData()[2]);
782
783
}

784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
// This test verifies that the option name is not mandatory, if the option
// code has been specified.
TEST_F(ParseConfigTest, optionDataNoName) {
    std::string config =
        "{ \"option-data\": [ {"
        "    \"space\": \"dhcp6\","
        "    \"code\": 23,"
        "    \"data\": \"2001:db8:1::1\""
        " } ]"
        "}";
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
}

// This test verifies that the option code is not mandatory, if the option
// name has been specified.
TEST_F(ParseConfigTest, optionDataNoCode) {
    std::string config =
        "{ \"option-data\": [ {"
        "    \"space\": \"dhcp6\","
        "    \"name\": \"dns-servers\","
        "    \"data\": \"2001:db8:1::1\""
        " } ]"
        "}";
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
}

// This test verifies that the option data configuration with a minimal
// set of parameters works as expected.
TEST_F(ParseConfigTest, optionDataMinimal) {
    std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"dns-servers\","
        "    \"data\": \"2001:db8:1::10\""
        " } ]"
        "}";
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());

    CfgMgr::instance().clear();
    // This time using an option code.
    config =
        "{ \"option-data\": [ {"
        "    \"code\": 23,"
        "    \"data\": \"2001:db8:1::20\""
        " } ]"
        "}";
    rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
                                                                   23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
}

860
861
862
863
864
865
866
867
868
869
870
// This test verifies that the option data configuration with a minimal
// set of parameters works as expected when option definition is
// created in the configruation file.
TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo-name\","
        "      \"code\": 2345,"
        "      \"type\": \"ipv6-address\","
        "      \"array\": True,"
871
        "      \"space\": \"dhcp6\""
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
        "  } ],"
        "  \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
        " } ]"
        "}";

    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 2345));
    ASSERT_TRUE(opt);
    ASSERT_EQ(2, opt->getAddresses().size());
    EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
    EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());

    CfgMgr::instance().clear();
    // Do the same test but now use an option code.
    config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo-name\","
        "      \"code\": 2345,"
        "      \"type\": \"ipv6-address\","
        "      \"array\": True,"
897
        "      \"space\": \"dhcp6\""
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
        "  } ],"
        "  \"option-data\": [ {"
        "    \"code\": 2345,"
        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
        " } ]"
        "}";

    rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
                                                                   2345));
    ASSERT_TRUE(opt);
    ASSERT_EQ(2, opt->getAddresses().size());
    EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
    EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());

}
916

917
918
919
// This test verifies an empty option data configuration is supported.
TEST_F(ParseConfigTest, emptyOptionData) {
    // Configuration string.
JINMEI Tatuya's avatar
JINMEI Tatuya committed
920
    const std::string config =
921
922
923
924
925
926
927
        "{ \"option-data\": [ {"
        "    \"name\": \"dhcp4o6-server-addr\""
        " } ]"
        "}";

    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
928
    EXPECT_EQ(0, rcode);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
929
    const Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
930
931
932
933
934
        Option6AddrLst>(getOptionPtr("dhcp6", D6O_DHCPV4_O_DHCPV6_SERVER));
    ASSERT_TRUE(opt);
    ASSERT_EQ(0, opt->getAddresses().size());
}

935
936
/// The next set of tests check basic operation of the HooksLibrariesParser.
//
937
938
// Convenience function to set a configuration of zero or more hooks
// libraries:
939
940
941
942
//
// lib1 - No parameters
// lib2 - Empty parameters statement
// lib3 - Valid parameters
943
944
945
std::string
setHooksLibrariesConfig(const char* lib1 = NULL, const char* lib2 = NULL,
                        const char* lib3 = NULL) {
946
947
948
949
950
951
952
953
    const string lbrace("{");
    const string rbrace("}");
    const string quote("\"");
    const string comma_space(", ");
    const string library("\"library\": ");
    const string parameters("\"parameters\": ");

    string config = string("{ \"hooks-libraries\": [");
954
    if (lib1 != NULL) {
955
956
957
958
959
        // Library 1 has no parameters
        config += lbrace;
        config += library + quote + std::string(lib1) + quote;
        config += rbrace;

960
        if (lib2 != NULL) {
961
962
963
964
965
966
            // Library 2 has an empty parameters statement
            config += comma_space + lbrace;
            config += library + quote + std::string(lib2) + quote + comma_space;
            config += string("\"parameters\": {}");
            config += rbrace;

967
            if (lib3 != NULL) {
968
969
970
971
972
973
974
975
976
                // Library 3 has valid parameters
                config += comma_space + lbrace;
                config += library + quote + std::string(lib3) + quote + comma_space;
                config += string("\"parameters\": {");
                config += string("    \"svalue\": \"string value\", ");
                config += string("    \"ivalue\": 42, ");     // Integer value
                config += string("    \"bvalue\": true");     // Boolean value
                config += string("}");
                config += rbrace;
977
978
979
980
981
982
983
984
985
986
            }
        }
    }
    config += std::string("] }");

    return (config);
}

// hooks-libraries element that does not contain anything.
TEST_F(ParseConfigTest, noHooksLibraries) {
987
988
989
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
990
991
992

    // Create an empty hooks-libraries configuration element.
    const string config = setHooksLibrariesConfig();
Stephen Morris's avatar
Stephen Morris committed
993
994

    // Verify that the configuration string parses.
995
    const int rcode = parseConfiguration(config);
996
997
    ASSERT_TRUE(rcode == 0) << error_text_;

998
    // Check that the parser recorded nothing.
999
    isc::hooks::HookLibsCollection libraries;
1000
1001
1002
    bool changed;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_FALSE(changed);
1003
    EXPECT_TRUE(libraries.empty());
1004

1005
1006
1007
    // Check that there are still no libraries loaded.
    hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
1008
}
1009

Stephen Morris's avatar
Stephen Morris committed
1010
// hooks-libraries element that contains a single library.
1011
TEST_F(ParseConfigTest, oneHooksLibrary) {
1012
1013
1014
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
1015

Stephen Morris's avatar
Stephen Morris committed
1016
    // Configuration with hooks-libraries set to a single library.
1017
1018
1019
1020
    const string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1);

    // Verify that the configuration string parses.
    const int rcode = parseConfiguration(config);
1021
1022
    ASSERT_TRUE(rcode == 0) << error_text_;

1023
    // Check that the parser recorded a single library.
1024
    HookLibsCollection libraries;
1025
    bool changed;
1026
1027
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_TRUE(changed);
1028
    ASSERT_EQ(1, libraries.size());
1029
    EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
1030
1031
1032
1033
1034

    // Check that the change was propagated to the hooks manager.
    hooks_libraries = HooksManager::getLibraryNames();
    ASSERT_EQ(1, hooks_libraries.size());
    EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
1035
}
1036

1037
1038
// hooks-libraries element that contains two libraries
TEST_F(ParseConfigTest, twoHooksLibraries) {
1039
1040
1041
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
1042

Stephen Morris's avatar
Stephen Morris committed
1043
    // Configuration with hooks-libraries set to two libraries.
1044
1045
    const string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
                                                  CALLOUT_LIBRARY_2);
1046

1047
1048
    // Verify that the configuration string parses.
    const int rcode = parseConfiguration(config);
1049
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
1050

1051
    // Check that the parser recorded two libraries in the expected order.
1052
    HookLibsCollection libraries;
1053
1054
1055
    bool changed;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_TRUE(changed);
1056
    ASSERT_EQ(2, libraries.size());
1057
1058
    EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
    EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
1059
1060
1061
1062
1063
1064

    // Verify that the change was propagated to the hooks manager.
    hooks_libraries = HooksManager::getLibraryNames();
    ASSERT_EQ(2, hooks_libraries.size());
    EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
    EXPECT_EQ(CALLOUT_LIBRARY_2, hooks_libraries[1]);
Stephen Morris's avatar
Stephen Morris committed
1065
1066
}

1067
1068
// Configure with two libraries, then reconfigure with the same libraries.
TEST_F(ParseConfigTest, reconfigureSameHooksLibraries) {
1069
1070
1071
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
Stephen Morris's avatar
Stephen Morris committed
1072

Stephen Morris's avatar
Stephen Morris committed
1073
    // Configuration with hooks-libraries set to two libraries.
1074
1075
    const std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
                                                       CALLOUT_LIBRARY_2);
Stephen Morris's avatar
Stephen Morris committed
1076

1077
1078
1079
1080
    // Verify that the configuration string parses. The twoHooksLibraries
    // test shows that the list will be as expected.
    int rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
1081

1082
1083
1084
    // The previous test shows that the parser correctly recorded the two
    // libraries and that they loaded correctly.

1085
    // Parse the string again.
1086
1087
    rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
1088

1089
1090
1091
1092
    // The list has not changed between the two parse operations. However,
    // the paramters (or the files they could point to) could have
    // changed, so the libraries are reloaded anyway.
    HookLibsCollection libraries;
1093
    bool changed;
1094
    hooks_libraries_parser_->getLibraries(libraries, changed);
1095
    EXPECT_TRUE(changed);
1096
    ASSERT_EQ(2, libraries.size());
1097
1098
    EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
    EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
1099
1100
1101
1102
1103
1104
1105

    // ... and check that the same two libraries are still loaded in the
    // HooksManager.
    hooks_libraries = HooksManager::getLibraryNames();
    ASSERT_EQ(2, hooks_libraries.size());
    EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
    EXPECT_EQ(CALLOUT_LIBRARY_2, hooks_libraries[1]);
Stephen Morris's avatar
Stephen Morris committed
1106
1107
}

1108
1109
// Configure the hooks with two libraries, then reconfigure with the same
// libraries, but in reverse order.
1110
TEST_F(ParseConfigTest, reconfigureReverseHooksLibraries) {
1111
1112
1113
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
Stephen Morris's avatar
Stephen Morris committed
1114

Stephen Morris's avatar
Stephen Morris committed
1115
    // Configuration with hooks-libraries set to two libraries.
1116
1117
    std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
                                                 CALLOUT_LIBRARY_2);
Stephen Morris's avatar
Stephen Morris committed
1118

1119
1120
    // Verify that the configuration string parses. The twoHooksLibraries
    // test shows that the list will be as expected.
Stephen Morris's avatar
Stephen Morris committed
1121
    int rcode = parseConfiguration(config);
1122
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
1123

1124
1125
    // A previous test shows that the parser correctly recorded the two
    // libraries and that they loaded correctly.
Stephen Morris's avatar
Stephen Morris committed
1126

1127
1128
1129
    // Parse the reversed set of libraries.
    config = setHooksLibrariesConfig(CALLOUT_LIBRARY_2, CALLOUT_LIBRARY_1);
    rcode = parseConfiguration(config);
1130
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
1131

1132
    // The list has changed, and this is what we should see.
1133
    HookLibsCollection libraries;
1134
1135
1136
    bool changed;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_TRUE(changed);
1137
    ASSERT_EQ(2, libraries.size());
1138
1139
    EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[0].first);
    EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[1].first);
1140
1141
1142
1143
1144
1145

    // ... and check that this was propagated to the HooksManager.
    hooks_libraries = HooksManager::getLibraryNames();
    ASSERT_EQ(2, hooks_libraries.size());
    EXPECT_EQ(CALLOUT_LIBRARY_2, hooks_libraries[0]);
    EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[1]);
1146
}
Stephen Morris's avatar
Stephen Morris committed
1147

1148
1149
1150
// Configure the hooks with two libraries, then reconfigure with
// no libraries.
TEST_F(ParseConfigTest, reconfigureZeroHooksLibraries) {
1151
1152
1153
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
1154

Stephen Morris's avatar
Stephen Morris committed
1155
    // Configuration with hooks-libraries set to two libraries.
1156
1157
1158
1159
1160
1161
1162
    std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
                                                 CALLOUT_LIBRARY_2);

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;

1163
1164
1165
    // A previous test shows that the parser correctly recorded the two
    // libraries and that they loaded correctly.

1166
1167
    // Parse the string again, this time without any libraries.
    config = setHooksLibrariesConfig();
1168
1169
    rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
1170

1171
    // The list has changed, and this is what we should see.
1172
    HookLibsCollection libraries;
1173
    bool changed;
1174
    hooks_libraries_parser_->getLibraries(libraries, changed);
1175
1176
    EXPECT_TRUE(changed);
    EXPECT_TRUE(libraries.empty());
1177
1178
1179
1180

    // Check that no libraries are currently loaded
    hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
Stephen Morris's avatar
Stephen Morris committed
1181
1182
}

1183
// Check with a set of libraries, some of which are invalid.
1184
TEST_F(ParseConfigTest, invalidHooksLibraries) {
1185
1186
1187
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
Stephen Morris's avatar
Stephen Morris committed
1188
1189
1190

    // Configuration string.  This contains an invalid library which should
    // trigger an error in the "build" stage.
1191
1192
1193
    const std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
                                                       NOT_PRESENT_LIBRARY,
                                                       CALLOUT_LIBRARY_2);
Stephen Morris's avatar
Stephen Morris committed
1194

1195
1196
    // Verify that the configuration fails to parse. (Syntactically it's OK,
    // but the library is invalid).
1197
    const int rcode = parseConfiguration(config);
1198
    ASSERT_FALSE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
1199

1200
1201
1202
    // Check that the message contains the library in error.
    EXPECT_FALSE(error_text_.find(NOT_PRESENT_LIBRARY) == string::npos) <<
        "Error text returned from parse failure is " << error_text_;
1203
1204
1205

    // Check that the parser recorded the names but, as they were in error,
    // does not flag them as changed.
1206
    HookLibsCollection libraries;
1207
1208
1209
1210
    bool changed;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_FALSE(changed);
    ASSERT_EQ(3, libraries.size());
1211
1212
1213
    EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
    EXPECT_EQ(NOT_PRESENT_LIBRARY, libraries[1].first);
    EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[2].first);
1214
1215
1216
1217

    // ...and check it did not alter the libraries in the hooks manager.
    hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
Stephen Morris's avatar
Stephen Morris committed
1218
}
1219

1220
1221
// Check that trying to reconfigure with an invalid set of libraries fails.
TEST_F(ParseConfigTest, reconfigureInvalidHooksLibraries) {
1222
1223
1224
    // Check that no libraries are currently loaded
    vector<string> hooks_libraries = HooksManager::getLibraryNames();
    EXPECT_TRUE(hooks_libraries.empty());
1225
1226
1227
1228
1229
1230

    // Configure with a single library.
    std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1);
    int rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;

1231
1232
1233
    // A previous test shows that the parser correctly recorded the two
    // libraries and that they loaded correctly.

1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
    // Configuration string.  This contains an invalid library which should
    // trigger an error in the "build" stage.
    config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1, NOT_PRESENT_LIBRARY,
                                     CALLOUT_LIBRARY_2);

    // Verify that the configuration fails to parse. (Syntactically it's OK,
    // but the library is invalid).
    rcode = parseConfiguration(config);
    EXPECT_FALSE(rcode == 0) << error_text_;

    // Check that the message contains the library in error.
    EXPECT_FALSE(error_text_.find(NOT_PRESENT_LIBRARY) == string::npos) <<
        "Error text returned from parse failure is " << error_text_;
1247
1248
1249

    // Check that the parser recorded the names but, as the library set was
    // incorrect, did not mark the configuration as changed.
1250
    HookLibsCollection libraries;
1251
1252
1253
1254
    bool changed;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_FALSE(changed);
    ASSERT_EQ(3, libraries.size());
1255
1256
1257
    EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
    EXPECT_EQ(NOT_PRESENT_LIBRARY, libraries[1].first);
    EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[2].first);
1258
1259
1260
1261
1262
1263
1264

    // ... but check that the hooks manager was not updated with the incorrect
    // names.
    hooks_libraries.clear();
    hooks_libraries = HooksManager::getLibraryNames();
    ASSERT_EQ(1, hooks_libraries.size());
    EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
Stephen Morris's avatar
Stephen Morris committed
1265
}
1266

Stephen Morris's avatar
Stephen Morris committed
1267
1268
// Check that if hooks-libraries contains invalid syntax, it is detected.
TEST_F(ParseConfigTest, invalidSyntaxHooksLibraries) {
1269

Stephen Morris's avatar
Stephen Morris committed
1270
1271
1272
1273
1274
1275
1276
1277
1278
    // Element holds a mixture of (valid) maps and non-maps.
    string config1 = "{ \"hooks-libraries\": [ "
        "{ \"library\": \"/opt/lib/lib1\" }, "
        "\"/opt/lib/lib2\" "
        "] }";
    string error1 = "one or more entries in the hooks-libraries list is not"
                    " a map";

    int rcode = parseConfiguration(config1);
1279
    ASSERT_NE(0, rcode);
Stephen Morris's avatar
Stephen Morris committed
1280
1281
1282
1283
1284
1285
1286
1287
1288
    EXPECT_TRUE(error_text_.find(error1) != string::npos) <<
        "Error text returned from parse failure is " << error_text_;

    // Element holds valid maps, except one where the library element is not
    // a string.
    string config2 = "{ \"hooks-libraries\": [ "
        "{ \"library\": \"/opt/lib/lib1\" }, "
        "{ \"library\": 123 } "
        "] }";
Stephen Morris's avatar
Stephen Morris committed
1289
1290
    string error2 = "value of 'library' element is not a string giving"
                    " the path to a hooks library";
Stephen Morris's avatar
Stephen Morris committed
1291
1292
1293
1294
1295

    rcode = parseConfiguration(config2);
    ASSERT_NE(0, rcode);
    EXPECT_TRUE(error_text_.find(error2) != string::npos) <<
        "Error text returned from parse failure is " << error_text_;
1296

Stephen Morris's avatar
Stephen Morris committed
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
    // Element holds valid maps, except one where the library element is the
    // empty string.
    string config3 = "{ \"hooks-libraries\": [ "
        "{ \"library\": \"/opt/lib/lib1\" }, "
        "{ \"library\": \"\" } "
        "] }";
    string error3 = "value of 'library' element must not be blank";

    rcode = parseConfiguration(config3);
    ASSERT_NE(0, rcode);
    EXPECT_TRUE(error_text_.find(error3) != string::npos) <<
        "Error text returned from parse failure is " << error_text_;

    // Element holds valid maps, except one where the library element is all
    // spaces.
    string config4 = "{ \"hooks-libraries\": [ "
        "{ \"library\": \"/opt/lib/lib1\" }, "
        "{ \"library\": \"      \" } "
        "] }";
    string error4 = "value of 'library' element must not be blank";

    rcode = parseConfiguration(config4);
    ASSERT_NE(0, rcode);
    EXPECT_TRUE(error_text_.find(error3) != string::npos) <<
        "Error text returned from parse failure is " << error_text_;

Stephen Morris's avatar
Stephen Morris committed
1323
1324
    // Element holds valid maps, except one that does not contain a
    // 'library' element.
Stephen Morris's avatar
Stephen Morris committed
1325
    string config5 = "{ \"hooks-libraries\": [ "
Stephen Morris's avatar
Stephen Morris committed
1326
1327
1328
1329
        "{ \"library\": \"/opt/lib/lib1\" }, "
        "{ \"parameters\": { \"alpha\": 123 } }, "
        "{ \"library\": \"/opt/lib/lib2\" } "
        "] }";
Stephen Morris's avatar
Stephen Morris committed
1330
    string error5 = "one or more hooks-libraries elements are missing the"
Stephen Morris's avatar
Stephen Morris committed
1331
1332
                    " name of the library";

Stephen Morris's avatar
Stephen Morris committed
1333
    rcode = parseConfiguration(config5);
Stephen Morris's avatar
Stephen Morris committed
1334
    ASSERT_NE(0, rcode);
Stephen Morris's avatar
Stephen Morris committed
1335
    EXPECT_TRUE(error_text_.find(error5) != string::npos) <<
1336
1337
1338
        "Error text returned from parse failure is " << error_text_;
}

1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369