summaryrefslogtreecommitdiffstats
path: root/vdstestlib
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /vdstestlib
Publish
Diffstat (limited to 'vdstestlib')
-rw-r--r--vdstestlib/.gitignore2
-rw-r--r--vdstestlib/CMakeLists.txt14
-rw-r--r--vdstestlib/OWNERS2
-rw-r--r--vdstestlib/src/.gitignore4
-rw-r--r--vdstestlib/src/testlist.txt2
-rw-r--r--vdstestlib/src/tests/cppunit/.gitignore4
-rw-r--r--vdstestlib/src/tests/cppunit/CMakeLists.txt9
-rw-r--r--vdstestlib/src/tests/cppunit/DESC1
-rw-r--r--vdstestlib/src/tests/cppunit/FILES1
-rw-r--r--vdstestlib/src/tests/cppunit/cppunittest.cpp25
-rw-r--r--vdstestlib/src/tests/cppunit/testrunner.cpp11
-rw-r--r--vdstestlib/src/tests/dirconfig/.gitignore5
-rw-r--r--vdstestlib/src/tests/dirconfig/CMakeLists.txt8
-rw-r--r--vdstestlib/src/tests/dirconfig/DESC1
-rw-r--r--vdstestlib/src/tests/dirconfig/FILES1
-rw-r--r--vdstestlib/src/tests/dirconfig/dirconfigtest.cpp105
-rw-r--r--vdstestlib/src/vespa/vdstestlib/.gitignore3
-rw-r--r--vdstestlib/src/vespa/vdstestlib/CMakeLists.txt8
-rw-r--r--vdstestlib/src/vespa/vdstestlib/cppunit/.gitignore3
-rw-r--r--vdstestlib/src/vespa/vdstestlib/cppunit/CMakeLists.txt7
-rw-r--r--vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.cpp151
-rw-r--r--vdstestlib/src/vespa/vdstestlib/cppunit/cppunittestrunner.h55
-rw-r--r--vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.cpp176
-rw-r--r--vdstestlib/src/vespa/vdstestlib/cppunit/dirconfig.h85
-rw-r--r--vdstestlib/src/vespa/vdstestlib/cppunit/macros.h206
-rw-r--r--vdstestlib/testrun/.gitignore19
26 files changed, 908 insertions, 0 deletions
diff --git a/vdstestlib/.gitignore b/vdstestlib/.gitignore
new file mode 100644
index 00000000000..a9b20e8992d
--- /dev/null
+++ b/vdstestlib/.gitignore
@@ -0,0 +1,2 @@
+Makefile
+Testing
diff --git a/vdstestlib/CMakeLists.txt b/vdstestlib/CMakeLists.txt
new file mode 100644
index 00000000000..98213a844fc
--- /dev/null
+++ b/vdstestlib/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_define_module(
+ DEPENDS
+ fastos
+ vespalib
+
+ TESTS
+ src/tests/cppunit
+ src/tests/dirconfig
+
+ LIBS
+ src/vespa/vdstestlib
+ src/vespa/vdstestlib/cppunit
+)
diff --git a/vdstestlib/OWNERS b/vdstestlib/OWNERS
new file mode 100644
index 00000000000..97c35339850
--- /dev/null
+++ b/vdstestlib/OWNERS
@@ -0,0 +1,2 @@
+vekterli
+dybdahl
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;
+}
diff --git a/vdstestlib/testrun/.gitignore b/vdstestlib/testrun/.gitignore
new file mode 100644
index 00000000000..4c3fd7fadbb
--- /dev/null
+++ b/vdstestlib/testrun/.gitignore
@@ -0,0 +1,19 @@
+/test-report.html
+/test-report.html.bottom
+/test-report.html.entry
+/test-report.html.summary
+/test-report.html.top
+/test.1.cppunit.desc.file.txt
+/test.1.cppunit.files.html
+/test.1.cppunit.log.file.txt
+/test.2.dirconfig.desc.file.txt
+/test.2.dirconfig.file.dirconfigtest.cpp.txt
+/test.2.dirconfig.files.html
+/test.2.dirconfig.log.file.txt
+/tmp.cppunit-time
+/tmp.cppunit.log-control
+/tmp.dirconfig-time
+/tmp.dirconfig.log-control
+/tmp.end-time
+/tmp.start-time
+/test.*.*.result