diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /vdstestlib/src |
Publish
Diffstat (limited to 'vdstestlib/src')
22 files changed, 871 insertions, 0 deletions
diff --git a/vdstestlib/src/.gitignore b/vdstestlib/src/.gitignore new file mode 100644 index 00000000000..201bafb341f --- /dev/null +++ b/vdstestlib/src/.gitignore @@ -0,0 +1,4 @@ +/Makefile.ini +/config_command.sh +/project.dsw +/vdstestlib.mak diff --git a/vdstestlib/src/testlist.txt b/vdstestlib/src/testlist.txt new file mode 100644 index 00000000000..48b3977cc93 --- /dev/null +++ b/vdstestlib/src/testlist.txt @@ -0,0 +1,2 @@ +tests/cppunit +tests/dirconfig diff --git a/vdstestlib/src/tests/cppunit/.gitignore b/vdstestlib/src/tests/cppunit/.gitignore new file mode 100644 index 00000000000..ec991c2f0cd --- /dev/null +++ b/vdstestlib/src/tests/cppunit/.gitignore @@ -0,0 +1,4 @@ +.depend +Makefile +testrunner +vdstestlib_testrunner_app diff --git a/vdstestlib/src/tests/cppunit/CMakeLists.txt b/vdstestlib/src/tests/cppunit/CMakeLists.txt new file mode 100644 index 00000000000..01356972a56 --- /dev/null +++ b/vdstestlib/src/tests/cppunit/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vdstestlib_testrunner_app + SOURCES + testrunner.cpp + cppunittest.cpp + DEPENDS + vdstestlib +) +vespa_add_test(NAME vdstestlib_testrunner_app NO_VALGRIND COMMAND vdstestlib_testrunner_app) diff --git a/vdstestlib/src/tests/cppunit/DESC b/vdstestlib/src/tests/cppunit/DESC new file mode 100644 index 00000000000..247fc25faf0 --- /dev/null +++ b/vdstestlib/src/tests/cppunit/DESC @@ -0,0 +1 @@ +Test of the CPPUNIT tools. diff --git a/vdstestlib/src/tests/cppunit/FILES b/vdstestlib/src/tests/cppunit/FILES new file mode 100644 index 00000000000..468a6bfb10a --- /dev/null +++ b/vdstestlib/src/tests/cppunit/FILES @@ -0,0 +1 @@ +programoptions.cpp diff --git a/vdstestlib/src/tests/cppunit/cppunittest.cpp b/vdstestlib/src/tests/cppunit/cppunittest.cpp new file mode 100644 index 00000000000..07c02f6dcdc --- /dev/null +++ b/vdstestlib/src/tests/cppunit/cppunittest.cpp @@ -0,0 +1,25 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/fastos/fastos.h> +#include <vespa/vdstestlib/cppunit/macros.h> + +namespace vespalib { + +struct CppunitTest : public CppUnit::TestFixture { + + void testSomething(); + + CPPUNIT_TEST_SUITE(CppunitTest); + CPPUNIT_TEST(testSomething); + CPPUNIT_TEST_SUITE_END(); + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(CppunitTest); + +void +CppunitTest::testSomething() +{ + CPPUNIT_ASSERT_EQUAL_MESSAGE("hmm", "foo", "foo"); +} + +} // vespalib diff --git a/vdstestlib/src/tests/cppunit/testrunner.cpp b/vdstestlib/src/tests/cppunit/testrunner.cpp new file mode 100644 index 00000000000..1ba7be7790e --- /dev/null +++ b/vdstestlib/src/tests/cppunit/testrunner.cpp @@ -0,0 +1,11 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/fastos/fastos.h> +#include <vespa/vdstestlib/cppunit/cppunittestrunner.h> + +int main(int argc, char **argv) +{ + vdstestlib::CppUnitTestRunner testRunner; + return testRunner.run(argc, argv); +} + diff --git a/vdstestlib/src/tests/dirconfig/.gitignore b/vdstestlib/src/tests/dirconfig/.gitignore new file mode 100644 index 00000000000..d19c58dc3e5 --- /dev/null +++ b/vdstestlib/src/tests/dirconfig/.gitignore @@ -0,0 +1,5 @@ +.depend +Makefile +dirconfig.tmp +dirconfig_test +vdstestlib_dirconfig_test_app diff --git a/vdstestlib/src/tests/dirconfig/CMakeLists.txt b/vdstestlib/src/tests/dirconfig/CMakeLists.txt new file mode 100644 index 00000000000..1cd06c06930 --- /dev/null +++ b/vdstestlib/src/tests/dirconfig/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vdstestlib_dirconfig_test_app + SOURCES + dirconfigtest.cpp + DEPENDS + vdstestlib +) +vespa_add_test(NAME vdstestlib_dirconfig_test_app NO_VALGRIND COMMAND vdstestlib_dirconfig_test_app) diff --git a/vdstestlib/src/tests/dirconfig/DESC b/vdstestlib/src/tests/dirconfig/DESC new file mode 100644 index 00000000000..2a3a952a471 --- /dev/null +++ b/vdstestlib/src/tests/dirconfig/DESC @@ -0,0 +1 @@ +Tests that dirconfig class works correctly. diff --git a/vdstestlib/src/tests/dirconfig/FILES b/vdstestlib/src/tests/dirconfig/FILES new file mode 100644 index 00000000000..6e94447a2bd --- /dev/null +++ b/vdstestlib/src/tests/dirconfig/FILES @@ -0,0 +1 @@ +dirconfigtest.cpp diff --git a/vdstestlib/src/tests/dirconfig/dirconfigtest.cpp b/vdstestlib/src/tests/dirconfig/dirconfigtest.cpp new file mode 100644 index 00000000000..d373b7de7e4 --- /dev/null +++ b/vdstestlib/src/tests/dirconfig/dirconfigtest.cpp @@ -0,0 +1,105 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/fastos/fastos.h> +#include <vespa/vdstestlib/cppunit/dirconfig.h> + +#include <fstream> +#include <iostream> +#include <vespa/log/log.h> +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/util/exceptions.h> + +LOG_SETUP("dirconfig_test"); + +using namespace vdstestlib; + +namespace vespalib { + +class Test : public vespalib::TestApp +{ +public: + void testNormalUsage(); + int Main(); +}; + +int +Test::Main() +{ + TEST_INIT("dirconfig_test"); + srandom(1); + testNormalUsage(); + TEST_DONE(); +} + +#define ASSERT_FILE_CONTENT(file, content) \ +{ \ + std::ifstream in(file); \ + std::ostringstream ost; \ + std::string line; \ + while (getline(in, line, '\n')) { \ + ost << line << '\n'; \ + } \ + EXPECT_EQUAL(content, ost.str()); \ +} + +void Test::testNormalUsage() { + DirConfig config1; + DirConfig config2; + + EXPECT_EQUAL("dir:dirconfig.tmp/1", config1.getConfigId()); + EXPECT_EQUAL("dir:dirconfig.tmp/2", config2.getConfigId()); + + try{ + config1.getConfig("testconfig"); + TEST_FATAL("Not supposed to get here"); + } catch (vespalib::Exception& e) { + EXPECT_EQUAL("No config named testconfig", e.getMessage()); + } + DirConfig::Config& file1(config1.addConfig("testconfig")); + try{ + config1.addConfig("testconfig"); + TEST_FATAL("Not supposed to get here"); + } catch (vespalib::Exception& e) { + EXPECT_EQUAL("There is already a config named testconfig", + e.getMessage()); + } + EXPECT_EQUAL(&file1, &config1.getConfig("testconfig")); + file1.set("intval", "5"); + file1.set("intval", "7"); + file1.set("stringval", "\"foo\""); + file1.set("tmpval", "4"); + file1.remove("tmpval"); + // Trigger publish + config1.getConfigId(); + + ASSERT_FILE_CONTENT("dirconfig.tmp/1/testconfig.cfg", + "intval 7\n" + "stringval \"foo\"\n"); + + DirConfig::Config& file2(config2.addConfig("testconfig")); + file2.set("longval", "6"); + file2.clear(); + file2.set("intval", "4"); + + DirConfig::Config& file3(config1.addConfig("config2")); + file3.set("intval", "3"); + file3.set("myarray[2]"); + file3.set("myarray[0].foo", "4"); + file3.set("myarray[1].foo", "2"); + + config1.publish(); + config2.publish(); + + ASSERT_FILE_CONTENT("dirconfig.tmp/2/testconfig.cfg", + "intval 4\n"); + ASSERT_FILE_CONTENT("dirconfig.tmp/1/config2.cfg", + "intval 3\n" + "myarray[2]\n" + "myarray[0].foo 4\n" + "myarray[1].foo 2\n"); + +} + +} // vespalib + +TEST_APPHOOK(vespalib::Test) diff --git a/vdstestlib/src/vespa/vdstestlib/.gitignore b/vdstestlib/src/vespa/vdstestlib/.gitignore new file mode 100644 index 00000000000..1b1547ac3e4 --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/.gitignore @@ -0,0 +1,3 @@ +/.depend +/Makefile +/libvdstestlib.so.5.1 diff --git a/vdstestlib/src/vespa/vdstestlib/CMakeLists.txt b/vdstestlib/src/vespa/vdstestlib/CMakeLists.txt new file mode 100644 index 00000000000..9671d35108b --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_library(vdstestlib + SOURCES + $<TARGET_OBJECTS:vdstestlib_vdstestlib_cppunit> + INSTALL lib64 + DEPENDS + cppunit +) diff --git a/vdstestlib/src/vespa/vdstestlib/cppunit/.gitignore b/vdstestlib/src/vespa/vdstestlib/cppunit/.gitignore new file mode 100644 index 00000000000..583460ae288 --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/cppunit/.gitignore @@ -0,0 +1,3 @@ +*.So +.depend +Makefile diff --git a/vdstestlib/src/vespa/vdstestlib/cppunit/CMakeLists.txt b/vdstestlib/src/vespa/vdstestlib/cppunit/CMakeLists.txt new file mode 100644 index 00000000000..f86ce997d0b --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/cppunit/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_library(vdstestlib_vdstestlib_cppunit OBJECT + SOURCES + cppunittestrunner.cpp + dirconfig.cpp + DEPENDS +) diff --git a/vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.cpp b/vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.cpp new file mode 100644 index 00000000000..38d5fe1e3c3 --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.cpp @@ -0,0 +1,151 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/fastos/fastos.h> +#include <vespa/vdstestlib/cppunit/cppunittestrunner.h> + +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <cppunit/ui/text/TestRunner.h> +#include <cppunit/TestSuite.h> +#include <cppunit/TextTestProgressListener.h> +#include <vespa/log/log.h> +#include <iostream> + +LOG_SETUP(".cppunittestrunner"); + +using CppUnit::Test; +using CppUnit::TestSuite; + +namespace vdstestlib { + +namespace { + struct WantedTestList : public CppUnit::Test::Filter { + std::vector<std::string> _wanted; + bool _includeStressTests; + + WantedTestList(int argc, const char * const * argv, + bool includeStressTests) + : _wanted(), + _includeStressTests(includeStressTests) + { + for (int i=1; i<argc; ++i) { + if (argv[i][0] != '-') { + std::cerr << "Running tests matching '*" + << argv[i] << "*'.\n"; + _wanted.push_back(argv[i]); + } + } + char* testpat = getenv("TEST_SUBSET"); + if (testpat != 0) { + std::string pat = std::string("*") + testpat; + if (pat[pat.size() - 1] != '$') pat += "*"; + std::cerr << "Running tests matching '" << pat << "'." + " (Taken from TEST_SUBSET environment variable)\n"; + _wanted.push_back(testpat); + } + if (CppUnit::Test::disabledCount > 0) { + std::cerr << CppUnit::Test::disabledCount + << " tests are currently disabled and won't be " + << "attempted run.\n"; + } + if (CppUnit::Test::ignoredCount > 0) { + std::cerr << CppUnit::Test::ignoredCount + << " tests are currently set to ignore failures.\n"; + } + } + + std::string getWantedString(uint32_t index) const { + std::string s = _wanted[index]; + if (s[s.size() - 1] == '$') { + return s.substr(0, s.size() - 1); + } + return s; + } + + bool requiresTailMatch(uint32_t index) const { + std::string s = _wanted[index]; + return (s[s.size() - 1] == '$'); + } + + virtual bool include(const std::string& name) const { + if ((name.find("stress") != std::string::npos || + name.find("Stress") != std::string::npos) + && !_includeStressTests) + { + std::cerr << "Excluding stress test " << name << "\n"; + } else { + if (_wanted.size() == 0) return true; + for (uint32_t i=0; i<_wanted.size(); ++i) { + std::string::size_type pos = name.rfind(getWantedString(i)); + if (pos == std::string::npos) continue; + if (!requiresTailMatch(i) + || pos == name.size() - getWantedString(i).size()) + { + return true; + } + } + } + return false; + } + }; + + struct LogHook : public CppUnit::TextTestProgressListener::TestStartHook { + std::string lastTest; + virtual void startedTest(const std::string& testName) { + LOG(info, "Starting test: %s", testName.c_str()); + lastTest = testName; + } + virtual void stoppedTest() { + LOG(info, "Stopped test: %s", lastTest.c_str()); + } + }; +} + +CppUnitTestRunner::CppUnitTestRunner() +{ + std::ios::sync_with_stdio(); +} + +int +CppUnitTestRunner::run(int argc, const char * const * argv) +{ + std::cout << "Running cppunit tests.\n"; + CppUnit::TextUi::TestRunner runner; + CppUnit::TestFactoryRegistry& registry( + CppUnit::TestFactoryRegistry::getRegistry()); + + Test *tests = registry.makeTest(); + TestSuite *suite = dynamic_cast<TestSuite *>(tests); + + bool includeStressTests = false; + bool logStartStop = false; + bool verbose = false; + if (getenv("TEST_VERBOSE") != 0) { + verbose = true; + } + + for (int i=1; i<argc; ++i) { + std::string arg(argv[i]); + if (arg == "--verbose") { + verbose = true; + logStartStop = true; + } else if (arg == "--includestress") { + includeStressTests = true; + } else if (argv[i][0] == '-') { + std::cerr << "Illegal option " << arg << "\n"; + exit(1); + } else { + // Arguments will be passed as patterns + } + } + + WantedTestList wantedList(argc, argv, includeStressTests); + suite->filter(wantedList); + runner.addTest(tests); + CppUnit::TextTestProgressListener::verboseProgress = verbose; + if (logStartStop) { + CppUnit::TextTestProgressListener::startHook.reset(new LogHook); + } + return (runner.run("", false) ? 0 : -1); +} + +} // vdstestlib diff --git a/vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.h b/vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.h new file mode 100644 index 00000000000..65a0ca4d434 --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.h @@ -0,0 +1,55 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @class CppUnitTestRunner + * @ingroup cppunit + * + * @brief Application for running cppunit tests. + * + * This is an application to use when running cppunit tests, currently used + * in document,vdslib,storageapi and storage. + * + * It is built like a library, as one need to create one per project, but the + * cppunit test application file in each project can now only contain a little + * main method, creating an instance of this class and calling run. + * + * See storage/src/cpp/tests/testrunner.h for an example of simple app using + * this. + * + * When using this test binary you have the following options. + * + * If the TEST_SUBSET environment variable is set, only tests matching the + * pattern given in the environment is run. For instance, by doing + * TEST_SUBSET=foo ./testrunner, only tests that match the regular expression + * .*foo.* will be run. Optionally you can postfix your expression with a + * dollar, to only run tests that end with the given string. You can match + * against any part of the function shown in verbose mode. For instance + * TEST_SUBSET=foo::bar$ will run tests whose test class ends in foo, and + * having test name bar. + * + * You can specify --verbose mode. In verbose mode, each test name is printed + * to stdout when started, and after completion, the runtime of the test is + * shown. This is very useful to identify slow unit tests which should be + * improved, and also to see in what test the system might be hung up in. In + * addition, in verbose mode, a vespa log entry is given at the start and end + * of each test, such that one can identify which parts of the vespa log belongs + * to each test, in case you are redirecting the log to a file. + * + * You can also use the --includestress option to also include tests that match + * the regular expression '.*[sS]tress.*'. These are excluded by default, such + * that regular test runs can be quick. + */ + +#pragma once + +namespace vdstestlib { + +class CppUnitTestRunner { +public: + CppUnitTestRunner(); + + int run(int argc, const char * const * argv); + +}; + +} // vdstestlib + diff --git a/vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.cpp b/vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.cpp new file mode 100644 index 00000000000..d93a1032ca3 --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.cpp @@ -0,0 +1,176 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/fastos/fastos.h> +#include <vespa/vdstestlib/cppunit/dirconfig.h> + +#include <fstream> +#include <vespa/log/log.h> +#include <sstream> +#include <vespa/vespalib/io/fileutil.h> +#include <vespa/vespalib/util/exceptions.h> + +LOG_SETUP(".dirconfig"); + +namespace vdstestlib { + + // When we start up first time, remove old config from the directories + // we're using +namespace { + std::string configRoot = "dirconfig.tmp"; + + int getFirstDirNumber() { + system(("rm -rf " + configRoot).c_str()); + return 0; + } +} + +unsigned int DirConfig::_nextDir(getFirstDirNumber()); + +DirConfig::Config::Config(const ConfigName& name) + : defFileName(name), + config(), + dirtyCache(false) +{ +} + +void +DirConfig::Config::set(const ConfigKey& key) +{ + set(key, ""); +} + +void +DirConfig::Config::set(const ConfigKey& key, const ConfigValue& value) +{ + for (std::list<std::pair<ConfigKey, ConfigValue> >::iterator it + = config.begin(); it != config.end(); ++it) + { + if (it->first == key) { + if (it->second == value) return; + dirtyCache = true; + it->second = value; + return; + } + } + dirtyCache = true; + config.push_back(std::pair<ConfigKey, ConfigValue>(key, value)); +} + +void +DirConfig::Config::remove(const ConfigKey& key) +{ + for (std::list<std::pair<ConfigKey, ConfigValue> >::iterator it + = config.begin(); it != config.end(); ++it) + { + if (it->first == key) { + dirtyCache = true; + config.erase(it); + return; + } + } +} + +const DirConfig::ConfigValue* +DirConfig::Config::get(const ConfigKey& key) const +{ + for (std::list<std::pair<ConfigKey, ConfigValue> >::const_iterator it + = config.begin(); it != config.end(); ++it) + { + if (it->first == key) { + return &it->second; + } + } + return 0; +} + +DirConfig::DirConfig() + : _configs(), + _dirName() +{ + std::ostringstream ost; + ost << configRoot << '/' << ++_nextDir; + _dirName = ost.str(); + vespalib::mkdir(_dirName, true); +} + +DirConfig::Config& +DirConfig::addConfig(const ConfigName& name) +{ + std::pair<std::map<ConfigName, Config>::iterator, bool> result + = _configs.insert( + std::map<ConfigName, Config>::value_type(name, Config(name))); + if (!result.second) { + throw vespalib::IllegalArgumentException( + "There is already a config named " + name, VESPA_STRLOC); + } + return result.first->second; +} + +DirConfig::Config& +DirConfig::getConfig(const ConfigName& name, bool createIfNonExisting) +{ + std::map<ConfigName, Config>::iterator it(_configs.find(name)); + if (it == _configs.end()) { + if (createIfNonExisting) { + return addConfig(name); + } + throw vespalib::IllegalArgumentException( + "No config named " + name, VESPA_STRLOC); + } + return it->second; +} + +void +DirConfig::removeConfig(const ConfigName& name) +{ + _configs.erase(name); +} + +void +DirConfig::publish() const +{ + for (std::map<ConfigName, Config>::const_iterator it = _configs.begin(); + it != _configs.end(); ++it) + { + std::string filename = _dirName + "/" + it->first + ".cfg"; + std::ofstream out(filename.c_str()); + for (std::list<std::pair<ConfigKey, ConfigValue> >::const_iterator i + = it->second.config.begin(); i != it->second.config.end(); ++i) + { + if (i->second.size() > 0) { + out << i->first << " " << i->second << "\n"; + } else { + out << i->first << "\n"; + } + } + out.close(); + LOG(debug, "Wrote config file %s.", filename.c_str()); + it->second.dirtyCache = false; + } +} + +std::string +DirConfig::getConfigId() const +{ + // Users are likely to set up config and then give config ids to users. + // This is thus a good place to automatically publish changes so users + // dont need to call publish manually + if (isCacheDirty()) { + LOG(debug, "Cache dirty in getConfigId(). Writing config files."); + publish(); + } + return "dir:" + _dirName; +} + +bool +DirConfig::isCacheDirty() const +{ + for (std::map<ConfigName, Config>::const_iterator it = _configs.begin(); + it != _configs.end(); ++it) + { + if (it->second.dirtyCache) return true; + } + return false; +} + +} // storage diff --git a/vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.h b/vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.h new file mode 100644 index 00000000000..0513628a74e --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.h @@ -0,0 +1,85 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * \class vdstestlib::DirConfig + * \ingroup cppunit + * + * \brief Helper class for generating dir config + * + * Some components use the same config identifier for config of multiple + * types. This can not be represented in file or raw config specifications. + * This helper class make it easy to use dir config, while not generating a + * lot of config files to check in, but keeping the config that needs to be + * changed programmatically in the unit test itself. + * + * To not make the class complex, all config entries are just key/value pairs. + * For string config entries make sure you include the double quotes in the + * value. + */ +#pragma once + +#include <boost/lexical_cast.hpp> +#include <list> +#include <map> +#include <string> + +namespace vdstestlib { + +struct DirConfig { + // Make some aliases to make it easy to see in header file what is what + typedef std::string ConfigName; + typedef std::string ConfigKey; + typedef std::string ConfigValue; + + struct Config { + ConfigName defFileName; + std::list<std::pair<ConfigKey, ConfigValue> > config; + mutable bool dirtyCache; + + Config(const ConfigName&); + + void clear() { config.clear(); } + void set(const ConfigKey&); // Set valueless key, such as array size + void set(const ConfigKey&, const ConfigValue&); + template<typename T> + void setValue(const ConfigKey& key, const T& value) + { + std::ostringstream ost; + ost << value; + set(key, ost.str()); + } + void remove(const ConfigKey&); + const ConfigValue* get(const ConfigKey&) const; + template<typename T> + T getValue(const ConfigKey& key, const T& defVal) const + { + const ConfigValue* val(get(key)); + if (val == 0) return defVal; + return boost::lexical_cast<T>(*val); + } + }; + + DirConfig(); + + // Adjusts the memory representation of this config. + // publish() to push the config from memory to files. + Config& addConfig(const ConfigName&); // Complain if existing + Config& getConfig(const ConfigName&, bool createIfNonExisting = false); + void removeConfig(const ConfigName&); + + /** Write the configs given to file. */ + void publish() const; + + /** Get the id that should be used to get config from this instance. */ + std::string getConfigId() const; + + /** Return whether memory representation currently differ from files. */ + bool isCacheDirty() const; + +private: + static unsigned int _nextDir; + std::map<ConfigName, Config> _configs; + std::string _dirName; +}; + +} // storage + diff --git a/vdstestlib/src/vespa/vdstestlib/cppunit/macros.h b/vdstestlib/src/vespa/vdstestlib/cppunit/macros.h new file mode 100644 index 00000000000..0665ec2e3df --- /dev/null +++ b/vdstestlib/src/vespa/vdstestlib/cppunit/macros.h @@ -0,0 +1,206 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * This file contains additional CPPUNIT macros to simplify tests. + */ +#pragma once +#include <cppunit/extensions/HelperMacros.h> + + +// Wrapper for CPPUNIT_ASSERT_EQUAL_MESSAGE to prevent it from evaluating +// message if val1 is equal to val2 +#define CPPUNIT_ASSERT_EQUAL_MSG(message, val1, val2) \ + { \ + if (!((val1) == (val2))) { \ + CPPUNIT_ASSERT_EQUAL_MESSAGE(message, val1, val2); \ + } \ + } + +#define CPPUNIT_ASSERT_EQUAL_ESCAPED(val1, val2) \ + { \ + if (!((val1) == (val2))) { \ + std::ostringstream out1; \ + std::ostringstream out2; \ + out1 << "[" << val1 << "]"; \ + out2 << "[" << val2 << "]"; \ + CPPUNIT_ASSERT_EQUAL( \ + document::StringUtil::escape(out1.str()), \ + document::StringUtil::escape(out2.str())); \ + } \ + } + +// Wrapper for CPPUNIT_ASSERT_MESSAGE to prevent it from evaluating message if +// val is true +#define CPPUNIT_ASSERT_MSG(message, val) \ + { \ + if (!(val)) { \ + CPPUNIT_ASSERT_MESSAGE(message, val); \ + } \ + } + +// Assert that value starts with prefix +#define CPPUNIT_ASSERT_PREFIX(prefix, value) \ + { \ + std::ostringstream pre; \ + pre << prefix; \ + std::ostringstream val; \ + val << value; \ + if (val.str().find(pre.str()) != 0) { \ + CPPUNIT_FAIL("Value of '" + val.str() + "' does not contain " \ + "prefix '" + pre.str() + "'."); \ + } \ + } + +// Assert that value contains given substring +#define CPPUNIT_ASSERT_CONTAIN(contained, value) \ + { \ + std::ostringstream cont; \ + cont << contained; \ + std::ostringstream val; \ + val << value; \ + if (val.str().find(cont.str()) == std::string::npos) { \ + CPPUNIT_FAIL("Value of '" + val.str() + "' does not contain '" \ + + cont.str() + "'."); \ + } \ + } + +// Assert that value contains given substring, add message to output on error +#define CPPUNIT_ASSERT_CONTAIN_MESSAGE(message, contained, value) \ + { \ + std::ostringstream cont; \ + cont << contained; \ + std::ostringstream val; \ + val << value; \ + std::string mess = message; \ + if (val.str().find(cont.str()) == std::string::npos) { \ + CPPUNIT_FAIL(mess + ": Value of '" + val.str() \ + + "' does not contain '" + cont.str() + "'."); \ + } \ + } + +// Assert that given expression matches the given regular expression. +#include <vespa/vespalib/util/regexp.h> +#define CPPUNIT_ASSERT_MATCH_REGEX(expression, value) \ + { \ + std::ostringstream _ost_; \ + _ost_ << value; \ + std::string _s_(_ost_.str()); \ + vespalib::Regexp _myregex_(expression); \ + if (!_myregex_.match(_s_)) { \ + CPPUNIT_FAIL("Value of '" + _s_ + "' does not match regex '" \ + + expression + "'."); \ + } \ + } + +// Assert that given expression matches the given regular expression. +#include <vespa/vespalib/util/regexp.h> +#define CPPUNIT_ASSERT_MATCH_REGEX_MSG(message, expression, value) \ + { \ + std::ostringstream _ost_; \ + _ost_ << value; \ + std::string _s_(_ost_.str()); \ + vespalib::Regexp _myregex_(expression); \ + std::string mess = message; \ + if (!_myregex_.match(_s_)) { \ + CPPUNIT_FAIL("Value of '" + _s_ + "' does not match regex '" \ + + expression + "'. Message: '" + mess + "'"); \ + } \ + } + +#define CPPUNIT_ASSERT_FILE_CONTAINS(expected, filename) \ + { \ + std::ostringstream value; \ + value << expected; \ + std::ostringstream ost; \ + std::string line; \ + std::ifstream input(filename); \ + while (std::getline(input, line, '\n')) { \ + ost << line << '\n'; \ + } \ + CPPUNIT_ASSERT_EQUAL(value.str(), ost.str()); \ + } + +#define CPPUNIT_ASSERT_SUBSTRING_COUNT(source, expectedCount, substring) \ + { \ + uint32_t count = 0; \ + std::ostringstream value; /* Let value be non-strings */ \ + value << source; \ + std::string s(value.str()); \ + std::string::size_type pos = s.find(substring); \ + while (pos != std::string::npos) { \ + ++count; \ + pos = s.find(substring, pos+1); \ + } \ + if (count != (uint32_t) expectedCount) { \ + std::ostringstream error; \ + error << "Value of '" << s << "' contained " << count \ + << " instances of substring '" << substring << "', not " \ + << expectedCount << " as expected."; \ + CPPUNIT_FAIL(error.str()); \ + } \ + } + +#include <iostream> +#include <map> +#include <unordered_map> +#include <vector> + +// Create output operator for containers. +// Needed so we can use CPPUNIT_ASSERT_EQUAL with them. + +// TODO: Remove these functions from the std namespace. +namespace std { + template<typename T> + inline std::ostream& operator<<(std::ostream& out, const std::vector<T>& v) + { + out << "std::vector(" << v.size() << ") {"; + for (uint32_t i=0, n=v.size(); i<n; ++i) { + out << "\n " << v[i]; + } + if (v.size() > 0) out << "\n"; + return out << "}"; + } + template<typename T> + inline std::ostream& operator<<(std::ostream& out, const std::set<T>& v) + { + out << "std::set(" << v.size() << ") {"; + for (typename std::set<T>::const_iterator it = v.begin(); it != v.end(); + ++it) + { + out << "\n " << *it; + } + if (v.size() > 0) out << "\n"; + return out << "}"; + } + template<typename S, typename T> + inline std::ostream& operator<<(std::ostream& out, const std::map<S, T>& m) + { + out << "std::map(" << m.size() << ") {"; + for (typename std::map<S, T>::const_iterator it = m.begin(); + it != m.end(); ++it) + { + out << "\n " << *it; + } + if (m.size() > 0) out << "\n"; + return out << "}"; + } + template<typename S, typename T> + inline std::ostream& operator<<(std::ostream& out, const std::pair<S, T>& p) + { + return out << "std::pair(" << p.first << ", " << p.second << ")"; + } +} + +template<typename S, typename T> +std::ostream& +operator<<(std::ostream& out, const std::unordered_map<S, T>& umap) +{ + out << "std::unordered_map(" << umap.size() << ") {"; + for (auto keyValue : umap) { + out << "\n " << keyValue.first << ": " << keyValue.second; + } + if (!umap.empty()) { + out << "\n"; + } + out << "}"; + return out; +} |