00001 /*!@file TestSuite/whitebox-Component.C */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00005 // by the University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/TestSuite/whitebox-Component.C $ 00035 // $Id: whitebox-Component.C 14376 2011-01-11 02:44:34Z pez $ 00036 // 00037 00038 #ifndef TESTSUITE_WHITEBOX_COMPONENT_C_DEFINED 00039 #define TESTSUITE_WHITEBOX_COMPONENT_C_DEFINED 00040 00041 #include "Component/ModelManager.H" 00042 #include "Component/ModelOptionDef.H" 00043 #include "Component/ModelParam.H" 00044 #include "Component/OptionManager.H" 00045 #include "Component/ParamClient.H" 00046 #include "Component/ParamMap.H" 00047 #include "TestSuite/TestSuite.H" 00048 #include "Util/sformat.H" 00049 00050 #include <exception> 00051 #include <vector> 00052 00053 namespace 00054 { 00055 // Dummy implementation of ParamClient that we can use to make sure 00056 // our model params are behaving as expected 00057 class TestParamClient : public ParamClient 00058 { 00059 public: 00060 bool param_has_registered, 00061 param_has_unregistered, 00062 param_has_changed, 00063 should_reject_changes; 00064 00065 TestParamClient() : 00066 param_has_registered(false), 00067 param_has_unregistered(false), 00068 param_has_changed(false), 00069 should_reject_changes(false) {} 00070 00071 virtual ~TestParamClient() {} 00072 00073 virtual void registerParam(ModelParamBase*) 00074 { param_has_registered = true; } 00075 00076 virtual void registerOptionedParam(OptionedModelParam*, int) 00077 { param_has_registered = true; } 00078 00079 virtual void unregisterParam(const ModelParamBase*) 00080 { param_has_unregistered = true; } 00081 00082 virtual void paramChanged(ModelParamBase* param, 00083 const bool valueChanged, 00084 ParamClient::ChangeStatus* status) 00085 { 00086 param_has_changed = true; 00087 if (should_reject_changes) 00088 *status = ParamClient::CHANGE_REJECTED; 00089 } 00090 }; 00091 00092 const ModelOptionDef OPT_testOption = 00093 { 00094 MODOPT_ARG(int), 00095 "testOption", 00096 &MOC_GENERAL, 00097 OPTEXP_CORE, 00098 "this is a test option", 00099 "call-it-like-this", 00100 's', 00101 NULL, 00102 "1" 00103 }; 00104 00105 class TestComponent : public ModelComponent 00106 { 00107 public: 00108 TestComponent(OptionManager& m) : 00109 ModelComponent(m, "test", "test") {} 00110 }; 00111 00112 // Like TestComponent, but with virtual inheritance 00113 class TestComponent2 : public virtual ModelComponent 00114 { 00115 public: 00116 TestComponent2(OptionManager& m) : 00117 ModelComponent(m, "test2", "test2") {} 00118 }; 00119 00120 class SlaveComponent : public ModelComponent 00121 { 00122 public: 00123 SlaveComponent(OptionManager& m) : 00124 ModelComponent(m, "slave", "slave"), 00125 itsParam(&OPT_testOption, this) 00126 {} 00127 00128 OModelParam<int> itsParam; 00129 }; 00130 00131 class MasterComponent : public ModelComponent 00132 { 00133 public: 00134 MasterComponent(OptionManager& m) : 00135 ModelComponent(m, "master", "master"), 00136 itsSub(new SlaveComponent(m)), 00137 itsParam(&OPT_testOption, this) 00138 { 00139 this->addSubComponent(itsSub); 00140 } 00141 00142 virtual void paramChanged(ModelParamBase* const param, 00143 const bool valueChanged, 00144 ParamClient::ChangeStatus* status) 00145 { 00146 if (param == &itsParam) 00147 { 00148 itsSub->setModelParamString("testOption", "42"); 00149 } 00150 } 00151 00152 nub::ref<SlaveComponent> itsSub; 00153 00154 OModelParam<int> itsParam; 00155 }; 00156 } 00157 00158 struct WeirdParamType 00159 { 00160 int x; 00161 00162 bool operator==(const WeirdParamType& that) { return this->x == that.x; } 00163 }; 00164 00165 void convertFromString(const std::string& str, WeirdParamType& p) 00166 { 00167 // this converter function always throws an exception, but it messes 00168 // with p's value beforehand; we do this so that we can make sure 00169 // that calling code is careful so that exceptions don't 00170 // unnecessarily garble param values 00171 p.x = -1; 00172 conversion_error::raise<WeirdParamType>(str); 00173 } 00174 00175 std::string convertToString(const WeirdParamType& p) 00176 { 00177 return convertToString(p.x); 00178 } 00179 00180 static void modelparam_xx_getset_xx_1(TestSuite& suite) 00181 { 00182 // do some idiot checks to make sure trivial things are working as 00183 // expected (e.g. set a value, then read it back... this tests that 00184 // the string conversions are working properly) 00185 00186 TestParamClient client; 00187 00188 NModelParam<int> p1("foo", &client, 1); 00189 00190 p1.setVal(2); 00191 REQUIRE_EQ(p1.getVal(), 2); 00192 00193 p1.setValString("13579"); 00194 REQUIRE_EQ(p1.getVal(), 13579); 00195 00196 p1.setVal(-45); 00197 REQUIRE_EQ(p1.getValString(), "-45"); 00198 00199 // now check that we correctly throw an exception for bogus 00200 // conversions: 00201 00202 bool caught; 00203 00204 try { caught = false; p1.setValString("abcde"); } 00205 catch (conversion_error& e) { caught = true; } 00206 REQUIRE(caught); 00207 00208 try { caught = false; p1.setValString(""); } 00209 catch (conversion_error& e) { caught = true; } 00210 REQUIRE(caught); 00211 } 00212 00213 static void modelparam_xx_getset_xx_2(TestSuite& suite) 00214 { 00215 TestParamClient client; 00216 00217 WeirdParamType v; v.x = 42; 00218 NModelParam<WeirdParamType> p1("foo", &client, v); 00219 00220 REQUIRE_EQ(p1.getValString(), std::string("42")); 00221 00222 bool caught; 00223 try { caught = false; p1.setValString(""); } 00224 catch (conversion_error& e) { caught = true; } 00225 REQUIRE(caught); 00226 00227 // make sure that p1's value is still the same even though an 00228 // exception was thrown during the setValString() call 00229 REQUIRE_EQ(p1.getValString(), std::string("42")); 00230 } 00231 00232 static void modelparam_xx_getset_xx_3(TestSuite& suite) 00233 { 00234 // Here we are testing the ParamClient::ChangeStatus system where 00235 // implementors of paramChanged() can reject a particular parameter 00236 // change by setting *status to ParamClient::CHANGE_REJECTED 00237 00238 TestParamClient client; 00239 00240 NModelParam<int> p1("foo", &client, 0); 00241 REQUIRE_EQ(p1.getVal(), 0); 00242 00243 p1.setVal(1); 00244 REQUIRE_EQ(p1.getVal(), 1); 00245 00246 client.should_reject_changes = true; 00247 p1.setVal(2); 00248 // the change should be rejected in paramChanged() which should 00249 // cause NModelParam to restore the original value in p1 00250 REQUIRE_EQ(p1.getVal(), 1); 00251 00252 client.should_reject_changes = false; 00253 p1.setVal(3); 00254 // now we should be allowing changes to get through again: 00255 REQUIRE_EQ(p1.getVal(), 3); 00256 } 00257 00258 static void modelparam_xx_readfrom_xx_1(TestSuite& suite) 00259 { 00260 // we want to ensure that any value change happening in readFrom() 00261 // gets passed on to the proper ParamClient 00262 00263 TestParamClient client; 00264 00265 REQUIRE(!client.param_has_registered); 00266 REQUIRE(!client.param_has_unregistered); 00267 REQUIRE(!client.param_has_changed); 00268 00269 { 00270 NModelParam<int> param("foo", &client, 42); 00271 00272 // make sure that the param registered with us 00273 REQUIRE(client.param_has_registered); 00274 REQUIRE(!client.param_has_unregistered); 00275 REQUIRE(!client.param_has_changed); 00276 00277 // make the param get its value from a ParamMap that won't 00278 // change its existing value 00279 ParamMap pmap1; 00280 pmap1.putIntParam("foo", 42); 00281 param.readFrom(pmap1, false); 00282 00283 REQUIRE(client.param_has_registered); 00284 REQUIRE(!client.param_has_unregistered); 00285 REQUIRE(!client.param_has_changed); 00286 00287 // now make the param get its value from a ParamMap that WILL change 00288 // its existing value 00289 ParamMap pmap2; 00290 pmap2.putIntParam("foo", 49); 00291 00292 param.readFrom(pmap2, false); 00293 00294 REQUIRE(client.param_has_registered); 00295 REQUIRE(!client.param_has_unregistered); 00296 REQUIRE(client.param_has_changed); 00297 } 00298 00299 // now the model param has gone out of scope and has been destroyed, 00300 // so it should have un-registered: 00301 00302 REQUIRE(client.param_has_registered); 00303 REQUIRE(client.param_has_unregistered); 00304 REQUIRE(client.param_has_changed); 00305 } 00306 00307 static void modelparam_xx_writeto_xx_1(TestSuite& suite) 00308 { 00309 TestParamClient client; 00310 00311 NModelParam<double> param("foo", &client, 3.5); 00312 00313 ParamMap pmap; 00314 param.writeTo(pmap); 00315 00316 REQUIRE_EQ(pmap.getDoubleParam("foo", 0.0), 3.5); 00317 } 00318 00319 static void modelmanager_xx_findoptiondef_xx_1(TestSuite& suite) 00320 { 00321 ModelManager m; 00322 m.setOptionValString(&OPT_testOption, "59"); 00323 std::string s = std::string("test") + "Option"; 00324 REQUIRE(m.findOptionDef(s.c_str()) == &OPT_testOption); 00325 REQUIRE_EQ(m.getOptionValString(&OPT_testOption), "59"); 00326 } 00327 00328 static void modelmanager_xx_defaultvalue_xx_1(TestSuite& suite) 00329 { 00330 ModelManager m; 00331 00332 ModelComponent c1(m, "c1", "c1"), c2(m, "c2", "c2"); 00333 00334 // set up an OModelParam that doesn't force its value as default 00335 OModelParam<int> p1(&OPT_testOption, &c1); 00336 REQUIRE_EQ(p1.getVal(), 1); 00337 p1.setVal(5); 00338 REQUIRE_EQ(p1.getVal(), 5); 00339 m.requestOption(p1, false); 00340 REQUIRE_EQ(p1.getVal(), 1); 00341 00342 // set up an OModelParam that DOES force its value as default 00343 OModelParam<int> p2(&OPT_testOption, &c2, 6, USE_MY_VAL); 00344 REQUIRE_EQ(p1.getVal(), 1); 00345 REQUIRE_EQ(p2.getVal(), 6); 00346 m.requestOption(p2, true); 00347 REQUIRE_EQ(p1.getVal(), 6); 00348 REQUIRE_EQ(p2.getVal(), 6); 00349 00350 m.setOptionValString(&OPT_testOption, "42"); 00351 REQUIRE_EQ(p1.getVal(), 42); 00352 REQUIRE_EQ(p1.getVal(), 42); 00353 } 00354 00355 static void modelcomponent_xx_forbidsharedptr_xx_1(TestSuite& suite) 00356 { 00357 ModelManager m; 00358 00359 bool got_exception = false; 00360 try { rutz::shared_ptr<ModelComponent> p(new TestComponent(m)); } 00361 catch (std::exception& e) { got_exception = true; } 00362 00363 REQUIRE(got_exception); 00364 } 00365 00366 static void modelcomponent_xx_forbidsharedptr_xx_2(TestSuite& suite) 00367 { 00368 ModelManager m; 00369 00370 try { rutz::shared_ptr<ModelComponent> p(new TestComponent2(m)); } 00371 catch (std::exception& e) {} 00372 00373 // FIXME: We don't have a good way to make this test actually work: 00374 /* REQUIRE(got_exception); */ 00375 00376 // The problem is that ModelComponent's pointer-checking mechanisms 00377 // runs into trouble when we have virtual inheritance. When somebody 00378 // inherits virtually from ModelComponent, then ModelComponent's 00379 // constructor has no way of knowing where the address of the full 00380 // object actually begins. 00381 } 00382 00383 static void modelcomponent_xx_root_object_xx_1(TestSuite& suite) 00384 { 00385 ModelManager m; 00386 00387 REQUIRE(m.getRootObject() == &m); 00388 REQUIRE(m.getParent() == 0); 00389 00390 nub::soft_ref<ModelComponent> c1(new TestComponent(m)); 00391 00392 REQUIRE(c1->getRootObject() == c1.get()); 00393 REQUIRE(c1->getParent() == 0); 00394 00395 m.addSubComponent(c1); 00396 00397 REQUIRE(c1->getRootObject() == &m); 00398 REQUIRE(c1->getParent() == &m); 00399 00400 nub::soft_ref<ModelComponent> c2(new TestComponent2(m)); 00401 00402 c1->addSubComponent(c2); 00403 00404 REQUIRE(c2->getRootObject() == &m); 00405 REQUIRE(c2->getParent() == c1.get()); 00406 } 00407 00408 #include "Util/Assert.H" 00409 00410 namespace 00411 { 00412 class ReentrantTester : public ModelComponent 00413 { 00414 public: 00415 ReentrantTester(OptionManager& mgr) 00416 : 00417 ModelComponent(mgr, "Reentrant Tester", "ReentrantTester"), 00418 itsOption(&OPT_testOption, this) 00419 {} 00420 00421 virtual void paramChanged(ModelParamBase* const param, 00422 const bool valueChanged, 00423 ParamClient::ChangeStatus* status) 00424 { 00425 if (param == &itsOption) 00426 { 00427 for (int i = 0; i < 10000; ++i) 00428 { 00429 itsNewParams.push_back 00430 (NModelParam<int>::make(sformat("foo%d", i), this, i)); 00431 } 00432 } 00433 } 00434 00435 OModelParam<int> itsOption; 00436 std::vector<rutz::shared_ptr<NModelParam<int> > > itsNewParams; 00437 }; 00438 } 00439 00440 static void modelcomponent_xx_reentrant_pinfos_xx_1(TestSuite& suite) 00441 { 00442 // Here we are testing that it is safe to create new model params on 00443 // the fly during a paramChanged() callback triggered by 00444 // exportOptions(); previously this had the potential to cause a 00445 // crash because ModelComponent's rep->pinfos would be appended to 00446 // (by the creation of a new NModelParam) thus invalidating 00447 // iterators, while we were in the midst of iterating over 00448 // rep->pinfos in exportOptions(). The new implementation of 00449 // ModelComponent::exportOptions() eschews iterators in favor of 00450 // simple iteration using array indexing, so that it is safe to 00451 // change rep->pinfos while we are iterating over it. 00452 00453 ModelManager m; 00454 00455 nub::ref<ReentrantTester> r(new ReentrantTester(m)); 00456 00457 m.addSubComponent(r); 00458 00459 m.exportOptions(MC_RECURSE); 00460 00461 REQUIRE_EQ(r->getNumModelParams(), size_t(10001)); 00462 } 00463 00464 static void modelcomponent_xx_master_slave_export_xx_1(TestSuite& suite) 00465 { 00466 ModelManager m; 00467 nub::ref<MasterComponent> master(new MasterComponent(m)); 00468 m.addSubComponent(master); 00469 m.exportOptions(MC_RECURSE); 00470 00471 // when exportOptions() is called on MasterComponent, it sets 00472 // SlaveComponent::itsParam to 42, and we are testing that 00473 // SlaveComponent::itsParam should not be overwritten by a later 00474 // exportOptions() calls -- that is, we are testing that the 00475 // implementation of exportOptions() first propagates to 00476 // subcomponents, and then exports the options of the calling 00477 // ModelComponent 00478 00479 REQUIRE_EQ(master->itsSub->itsParam.getVal(), 42); 00480 } 00481 00482 /////////////////////////////////////////////////////////////////////// 00483 // 00484 // main 00485 // 00486 /////////////////////////////////////////////////////////////////////// 00487 00488 int main(int argc, const char** argv) 00489 { 00490 TestSuite suite; 00491 00492 suite.ADD_TEST(modelparam_xx_getset_xx_1); 00493 suite.ADD_TEST(modelparam_xx_getset_xx_2); 00494 suite.ADD_TEST(modelparam_xx_getset_xx_3); 00495 suite.ADD_TEST(modelparam_xx_readfrom_xx_1); 00496 suite.ADD_TEST(modelparam_xx_writeto_xx_1); 00497 suite.ADD_TEST(modelmanager_xx_findoptiondef_xx_1); 00498 suite.ADD_TEST(modelmanager_xx_defaultvalue_xx_1); 00499 #if defined(GVX_MEM_DEBUG) 00500 suite.ADD_TEST(modelcomponent_xx_forbidsharedptr_xx_1); 00501 suite.ADD_TEST(modelcomponent_xx_forbidsharedptr_xx_2); 00502 #else 00503 // prevent "unused static function" warnings: 00504 (void) &modelcomponent_xx_forbidsharedptr_xx_1; 00505 (void) &modelcomponent_xx_forbidsharedptr_xx_2; 00506 #endif 00507 suite.ADD_TEST(modelcomponent_xx_root_object_xx_1); 00508 00509 suite.ADD_TEST(modelcomponent_xx_reentrant_pinfos_xx_1); 00510 00511 suite.ADD_TEST(modelcomponent_xx_master_slave_export_xx_1); 00512 00513 suite.parseAndRun(argc, argv); 00514 00515 return 0; 00516 } 00517 00518 // ###################################################################### 00519 /* So things look consistent in everyone's emacs... */ 00520 /* Local Variables: */ 00521 /* indent-tabs-mode: nil */ 00522 /* End: */ 00523 00524 #endif // TESTSUITE_WHITEBOX_COMPONENT_C_DEFINED