From 8b81c0c1e773819f1d531b4d44388193bb93fd9e Mon Sep 17 00:00:00 2001 From: Daniel Burrows Date: Mon, 24 May 2010 09:36:33 -0700 Subject: Start using google-mock to test the search input controller logic. I use google-mock to create a mock implementation of the view, then test that the logic interacts with it as expected. Right now, it doesn't. The ability to do this was part of why I wanted to separate the logic from the view. I haven't added google-mock to configure.ac yet; need to do that later. --- site_scons/aptitude_configure.py | 3 + site_scons/aptitude_configure_checks.py | 27 +++++++ src/gtk/views/mocks/search_input.h | 70 +++++++++++++++++ tests/Makefile.am | 7 +- tests/SConscript | 28 +++++++ tests/boost_test_main.cc | 11 +++ tests/test_search_input_controller.cc | 131 ++++++++++++++++++++++++++++++++ 7 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/gtk/views/mocks/search_input.h create mode 100644 tests/test_search_input_controller.cc diff --git a/site_scons/aptitude_configure.py b/site_scons/aptitude_configure.py index f46a31b5..46b4c184 100644 --- a/site_scons/aptitude_configure.py +++ b/site_scons/aptitude_configure.py @@ -132,6 +132,9 @@ the Boost unit tests need.''' TryLibrary('boost_unit_test_framework') ]), "Can't find Boost.Test") + RequireCheck(conf.CheckForGoogleMock(), + "Can't find google-mock") + conf.Finish() def DoConfigureCppunitTests(env): diff --git a/site_scons/aptitude_configure_checks.py b/site_scons/aptitude_configure_checks.py index 75eee9cc..fd6006f9 100644 --- a/site_scons/aptitude_configure_checks.py +++ b/site_scons/aptitude_configure_checks.py @@ -236,6 +236,33 @@ int main(int argc, char **argv) boost::iostreams::filtering_ostream compressed_devnull(boost::iostreams::zlib_compressor(9) | devnull); }''', context.env['CXXFILESUFFIX']) +@ConfigureCheck("Checking for google-mock") +def CheckForGoogleMock(context): + """Look for gmock (headers and library). + +Brings gtest along for the ride, because otherwise gmock won't link.""" + + context.env.Append(LIBS = [ 'gmock', 'gtest' ]) + + return context.TryLink(''' +#include + +class FooMock +{ +public: + MOCK_METHOD1(foo, void(int)); +}; + +int main(int argc, char **argv) +{ + ::testing::GTEST_FLAG(throw_on_failure) = true; + ::testing::InitGoogleMock(&argc, argv); + + FooMock mock; + mock.foo(4); +}''', context.env['CXXFILESUFFIX']) + + @ConfigureCheck("Checking for Boost.Test") def CheckForBoostTest(context): """Look for Boost.Test.""" diff --git a/src/gtk/views/mocks/search_input.h b/src/gtk/views/mocks/search_input.h new file mode 100644 index 00000000..f70a0b5e --- /dev/null +++ b/src/gtk/views/mocks/search_input.h @@ -0,0 +1,70 @@ +/** \file search_input.h */ // -*-c++-*- + +// Copyright (C) 2010 Daniel Burrows +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, write to +// the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. + +#ifndef APTITUDE_GTK_VIEWS_MOCKS_SEARCH_INPUT_H +#define APTITUDE_GTK_VIEWS_MOCKS_SEARCH_INPUT_H + +#include +#include + +namespace gui +{ + namespace views + { + namespace mocks + { + /** \brief Mock implementation of search_input for use in unit + * tests. + * + * Provides signals which, by default, are connected by the + * connect_() methods. + */ + class search_input : public views::search_input + { + public: + MOCK_METHOD0(get_search_text, Glib::ustring()); + MOCK_METHOD1(set_search_text, void(const Glib::ustring &)); + MOCK_METHOD1(set_error_message, void(const Glib::ustring &)); + MOCK_METHOD1(set_input_validity, void(bool)); + MOCK_METHOD1(set_find_sensitivity, void(bool)); + MOCK_METHOD1(connect_search_text_changed, sigc::connection(const sigc::slot &)); + MOCK_METHOD1(connect_search, sigc::connection(const sigc::slot &)); + + sigc::signal signal_search_text_changed; + sigc::signal signal; + + search_input() + { + using testing::_; + using testing::Invoke; + + ON_CALL(*this, connect_search_text_changed(_)) + .WillByDefault(Invoke(&signal_search_text_changed, + &sigc::signal::connect)); + + ON_CALL(*this, connect_search(_)) + .WillByDefault(Invoke(&signal, + &sigc::signal::connect)); + } + }; + } + } +} + +#endif // APTITUDE_GTK_VIEWS_MOCKS_SEARCH_INPUT_H diff --git a/tests/Makefile.am b/tests/Makefile.am index 9a630f0d..755c632d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -2,13 +2,17 @@ MAINTAINERCLEANFILES = Makefile.in INCLUDES = -I$(top_builddir) -I$(top_srcdir) -I$(top_srcdir)/src -I$(srcdir) BOOST_TEST_LDFLAGS = @BOOST_UNIT_TEST_LIBS@ +GMOCK_LDFLAGS = -lgmock -lgtest AM_CPPFLAGS = -DBOOST_TEST_DYN_LINK -DSRCDIR=\"$(srcdir)\" LDADD = $(top_builddir)/src/loggers.o \ +$(top_builddir)/src/gtk/controllers/libgtk-controllers.a \ +$(top_builddir)/src/gtk/views/libgtk-views.a \ $(top_builddir)/src/generic/apt/matching/libgeneric-matching.a \ $(top_builddir)/src/generic/apt/libgeneric-apt.a \ $(top_builddir)/src/generic/apt/matching/libgeneric-matching.a \ $(top_builddir)/src/generic/problemresolver/libgeneric-problemresolver.a \ -$(top_builddir)/src/generic/util/libgeneric-util.a -lcppunit $(BOOST_TEST_LDFLAGS) +$(top_builddir)/src/generic/util/libgeneric-util.a -lcppunit \ +$(BOOST_TEST_LDFLAGS) $(GMOCK_LDFLAGS) check_PROGRAMS = cppunit_test boost_test @@ -51,4 +55,5 @@ boost_test_SOURCES = \ test_dynamic_set.cc \ test_enumerator.cc \ test_file_cache.cc \ + test_search_input_controller.cc \ test_sqlite.cc diff --git a/tests/SConscript b/tests/SConscript index 658d47c0..b41e3358 100644 --- a/tests/SConscript +++ b/tests/SConscript @@ -67,15 +67,43 @@ boost_test_sources = [ 'test_dynamic_set.cc', 'test_enumerator.cc', 'test_file_cache.cc', + 'test_search_input_controller.cc', 'test_sqlite.cc' ] boost_test_extra_deps = [ '../src/loggers.o', + '../src/generic/apt/apt.o', + '../src/generic/apt/aptcache.o', + '../src/generic/apt/aptitude_resolver.o', + '../src/generic/apt/aptitude_resolver_cost_settings.o', + '../src/generic/apt/aptitude_resolver_cost_syntax.o', + '../src/generic/apt/aptitude_resolver_cost_types.o', + '../src/generic/apt/aptitude_resolver_universe.o', + '../src/generic/apt/aptitudepolicy.o', + '../src/generic/apt/config_signal.o', + '../src/generic/apt/download_queue.o', + '../src/generic/apt/dump_packages.o', + '../src/generic/apt/matching/compare_patterns.o', + '../src/generic/apt/matching/match.o', + '../src/generic/apt/matching/parse.o', + '../src/generic/apt/matching/pattern.o', + '../src/generic/apt/matching/serialize.o', + '../src/generic/apt/pkg_hier.o', + '../src/generic/apt/resolver_manager.o', + '../src/generic/apt/tags.o', + '../src/generic/apt/tasks.o', + '../src/generic/problemresolver/cost.o', + '../src/generic/problemresolver/cost_limits.o', + '../src/generic/problemresolver/incremental_expression.o', '../src/generic/util/file_cache.o', + '../src/generic/util/refcounted_base.o', '../src/generic/util/sqlite.o', '../src/generic/util/temp.o', + '../src/generic/util/undo.o', '../src/generic/util/util.o', + '../src/gtk/controllers/search_input.o', + '../src/gtk/views/search_input.o', ] test_data_files = [ diff --git a/tests/boost_test_main.cc b/tests/boost_test_main.cc index 40bf9eea..a4732d51 100644 --- a/tests/boost_test_main.cc +++ b/tests/boost_test_main.cc @@ -4,6 +4,8 @@ #include +#include + #include #include #include @@ -27,6 +29,15 @@ int main(int argc, char **argv) { argv0 = argv[0]; + // TODO: setting throw_on_failure is a hack; instead, we should + // configure the mock framework to emit Boost.Test errors. + // Unfortunately, I can't access the documentation (for the Google + // Test event listener API) that would tell me how to do this + // without a network connection. I hate libraries without offline + // documentation. + ::testing::GTEST_FLAG(throw_on_failure) = true; + ::testing::InitGoogleMock(&argc, argv); + bool debug = false; for(int i = 1; i < argc; ++i) { diff --git a/tests/test_search_input_controller.cc b/tests/test_search_input_controller.cc new file mode 100644 index 00000000..0eb54ba0 --- /dev/null +++ b/tests/test_search_input_controller.cc @@ -0,0 +1,131 @@ +/** \file test_search_input.cc */ + +// Copyright (C) 2010 Daniel Burrows +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, write to +// the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. + +#include +#include +#include +#include + +#include +#include + +namespace ctrls = gui::controllers; +namespace views = gui::views; +namespace mocks = gui::views::mocks; + +using aptitude::matching::compare_patterns; +using aptitude::matching::pattern; +using boost::make_shared; +using boost::shared_ptr; +using cwidget::util::ref_ptr; +using testing::InSequence; +using testing::NiceMock; +using testing::StrictMock; + +namespace +{ + MATCHER_P(PatternsEqual, + expected, + "") + { + return compare_patterns(arg, expected) == 0; + } + + // Mock class that lets us capture the controller's outgoing + // signals. + class controller_callbacks + { + public: + MOCK_METHOD2(activated, void(Glib::ustring, cwidget::util::ref_ptr)); + }; + + struct SearchInputTest + { + boost::shared_ptr view; + boost::shared_ptr viewNice; + boost::shared_ptr viewStrict; + + controller_callbacks callbacks; + controller_callbacks callbacksNice; + controller_callbacks callbacksStrict; + + // Controllers are initialized lazily, to give us a chance to + // register expectations with the views first. + private: + boost::shared_ptr controller; + boost::shared_ptr controllerNice; + boost::shared_ptr controllerStrict; + + public: + boost::shared_ptr get_controller() + { + if(controller.get() == NULL) + { + controller = ctrls::create_search_input(view); + controller->connect_activated(sigc::mem_fun(callbacks, + &controller_callbacks::activated)); + } + return controller; + } + + boost::shared_ptr get_controllerNice() + { + if(controllerNice.get() == NULL) + { + controllerNice = ctrls::create_search_input(view); + controllerNice->connect_activated(sigc::mem_fun(callbacksNice, + &controller_callbacks::activated)); + } + return controllerNice; + } + + boost::shared_ptr get_controllerStrict() + { + if(controllerStrict.get() == NULL) + { + controllerStrict = ctrls::create_search_input(view); + controllerStrict->connect_activated(sigc::mem_fun(callbacksStrict, + &controller_callbacks::activated)); + } + return controllerStrict; + } + + SearchInputTest() + : view(make_shared()), + viewNice(make_shared >()), + viewStrict(make_shared >()) + { + } + }; +} + +BOOST_FIXTURE_TEST_CASE(testEnteringCorrectTextSearches, SearchInputTest) +{ + ref_ptr p = pattern::make_installed(); + Glib::ustring p_text = "?installed"; + + { + InSequence dummy; + + EXPECT_CALL(*view, set_search_text(p_text)); + EXPECT_CALL(callbacks, activated(p_text, PatternsEqual(p))); + } + + get_controller()->enter_text(p_text); +} -- cgit v1.2.3