Back to home page

Project CMSSW displayed by LXR

 
 

    


Warning, /FWCore/TestProcessor/Readme.md is written in an unsupported language. File is not indexed.

0001 # FWCore/TestProcessor Documentation
0002 
0003 ## Introduction
0004 The `TestProcessor` class is used to test individual CMSSW framework modules. The class allows one to 
0005 
0006 * Specify a string containing the python configuration of the module and
0007 * Pass data from the test to the module through the edm::Event and/or the edm::EventSetup
0008 
0009 The system is composed of three parts
0010 
0011 1. A python `TestProcess` class
0012 1. A C++ configuration class
0013 1. The C++ `TestProcessor` class
0014 
0015 ## Python `TestProcess` class
0016 The `TestProcess` class has all the same attributes as the standard `cms.Process` class except
0017 
0018 * If a process name is not give, it will default to `"TEST"`
0019 * `cms.Path` and `cms.EndPath` are ignored
0020 * the method `moduleToTest` was added and is used to specify which module is to be called during the test. This method also accepts a `cms.Task` which is used to run any other modules that may be needed.
0021 
0022 As stated above, additional framework modules and EventSetup modules and Services are allowed to be specified in the configuration. If no EventSetup modules are specified in the `cms.Task` passed to `moduleToTest` than all EventSetup modules loaded into `TestProcess` will be created for the test. The same holds for Services.
0023 
0024 ### Example 1: Setup only the module
0025 
0026 ```python
0027 from FWCore.TestProcessor.TestProcess import *
0028 process = TestProcess()
0029 process.foo = cms.EDProducer('FooProd')
0030 process.moduleToTest(process.foo)
0031 ```
0032 
0033 ### Example 2: Use additional modules
0034 
0035 ```python
0036 from FWCore.TestProcessor.TestProcess import *
0037 process = TestProcess()
0038 process.load("somePackage.someModules_cff")
0039 process.foo = cms.EDProducer('FooProd')
0040 process.moduleToTest(process.foo, cms.Task(process.bar))
0041 ```
0042 
0043 NOTE: We recommend testing modules completely in isolation, however we realize, for some cases, that is not practical.
0044 
0045 ## C++ Configuration Class
0046 The configuration class `edm::test::TestProcessor::Config` is used to 
0047 
0048 * hold a C++ string containing the python configuration using `TestProcess`
0049 * register Event data products that the test may be using
0050 * register EventSetup data products that th etest may be using
0051 * register additional process names which simulates reading data from a file
0052 
0053 The configuration class is separate from the `edm::test::TestProcessor` class since once the latter is setup, one can not change its behavior (i.e. you can not add additional event data products to produce).
0054 
0055 The same configuration class can be reused to setup multiple `edm::test::TestProcessor` instances.
0056 
0057 ### Example 1: Use only the python configuration
0058 We recommend using a C++ raw string for the python configuration. That way line breaks and quotation marks are automatically handled.
0059 
0060 ```cpp
0061 edm::test::TestProcessor::Config config{
0062 R"_(from FWCore.TestProcessor.TestProcess import *
0063 process = TestProcess()
0064 process.foo = cms.EDProducer("FooProd")
0065 process.moduleToTest(process.foo)
0066 )_"
0067 };
0068 ```
0069     
0070 ### Example 2: Adding Event data products
0071 You must register which Event data products you intend to pass to the test. A `edm::EDPutTokenT<>` object is returned from the registration call. This object must be passed to the test along with an `std::unique_ptr<>` containing the object. Multiple Event data products are allowed.
0072 
0073 ```cpp
0074 edm::test::TestProcessor::Config config{
0075 R"_(...
0076 )_"
0077 };
0078 
0079 //Uses the module label 'bar'
0080 auto barPutToken = config.produces<std::vector<Bar>>("bar");
0081 ```
0082     
0083 ### Example 3: Adding Event data products from an earlier Process
0084 To add an addition process to the test, one must call the `addExtraProcess` method. This method returns a `edm::test::ProcessToken`. This token is then passed to the `produces` call. If a `edm::test::ProcessToken` is not passed, the data product is set to come from the most _recent_ process.
0085 One can specify multiple extra processes. The order of the calls determines the order of the process histories. With the first call creating the oldest process.
0086 
0087 ```cpp
0088 auto hltProcess = config.addExtraProcess("HLT");
0089 auto barPutToken = config.produces<std::vector<Bar>>("bar","",hltProcess);
0090 ```
0091 
0092 ###Example 4: Adding EventSetup data products
0093 You must register which EventSetup data products you intend to pass to the test as well as the EventSetup Record from which the data product can be obtained. A `edm::test::ESPutTokenT<>` object is returned from the registration call. This object must be passed to the test along with an `std::unique_ptr<>` containing the object. Multiple EventSetup data products are allowed.
0094 
0095 ```cpp
0096 auto esPutToken = config.esProduces<FooData,FooRecord>();
0097 ```
0098 
0099 ## `TestProcessor` class
0100 
0101 `edm::test::TestProcessor` does the work of running the tests via its `test()` method. An instance of the class is constructed by passing it an instance of `edm::test::TestProcessor::Config`. In the constructor, the class will load all the framework modules needed by the python configuration as well as setup all internal details needed to run CMSSW framework transitions.
0102 
0103 The first call to `test()` will cause the _beginJob_, _beginStream_, _globalBeginRun_, _streamBeginRun_, _globalBeginLuminosityBlock_ and _streamBeginLuminosityBlock_ transitions of the modules to be called in addition to the _event_ transition. Subsequent calls to `test()` will only generate new _event_ transitions. 
0104 
0105 Calling `setRunNumber()` or `setLuminosityBlockNumber()` will trigger the appropriate _begin_ transition calls and will be preceeded by the appropriate _end_ transition calls if a call to `test()` was already made.
0106 
0107 The `test()` method accepts an unlimited number of arguments of the type `std::pair<edm::EDPutTokenT<T>,std::unique_ptr<T>>` and `std::pair<edm::test::ESPutTokenT<T>, std::unique_ptr<T>`. Each call to `test()` will also generate a new EventSetup IOV for the Records declared during the EventSetup data product registration calls to `edm::test::TestProcessor::Config`. This allows each call to `test()` to update the EventSetup data product to be used.
0108   
0109 ### `edm::test::Event`
0110 The return value of `test()` is an `edm::test::Event`. The `Event` class gives access to any data products created by the module being tested via a call to `get<T>()`. Since the module label and the process name are already known by the test, `get<T>()` takes one optional argument which is the _productInstanceLabel_ for the data product. The call to `get<T>()` returns an `edm::test::TestHandle<T>` which acts like a smart pointer to the data product if the product was retrieved. If the data product was not retrieved, attempting to access the data will cause an exception.
0111   
0112 The `edm::test::Event` also has the method `modulePassed()` which is only useful when testing an `EDFilter`. In that case, the method returns `true` if the module passed the Event and `false` otherwise.
0113 
0114 ### Run and LuminosityBlock product testing
0115 It is possible to also test Run and LuminosityBlock products created by the module. This can be accomplished by calling
0116 * `testBeginRun(edm::RunNumber_t)`
0117 * `testEndRun()`
0118 * `testBeginLuminosityBlock(edm::LuminosityBlockNumber_t)`
0119 * `testEndLuminosityBlock()`
0120 
0121 Like `test()` one can pass an unlimited number of arguments of type  `std::pair<edm::test::ESPutTokenT<T>, std::unique_ptr<T>` in order to control EventSetup data passed to the module (`EDPutToken` are not supported at this time). Each of the above `test` functions also return either `edm::test::Run` or `edm::test::LuminosityBlock` which has the same interface as `edm::test::Event` except they do not have the method `modulePassed()` because filtering does not occur on those transitions. 
0122 
0123 The `test` methods all make sure all the needed transitions occur in the necessary order. For example, calling `test()` and then `testEndRun()` will cause _streamEndLuminosityBlock_ and _globalEndLuminosityBlock_ to occur.
0124 
0125 When calling `testBeginRun(edm:RunNumber_t)` or `testBeginLuminosityBlock(edm::LuminosityBlockNumber_t)` one is required to pass in a Run or LuminosityBlock number which is different from the number previously used by any earlier calls to a `test` function. 
0126 
0127 
0128 ### Full Example 
0129 
0130 ```cpp
0131 #include "FWCore/TestProcessor/interface/TestProcessor.h"
0132 int main() {
0133   //The python configuration
0134   edm::test::TestProcessor::Config config{
0135   R"_(from FWCore.TestProcessor.TestProcess import *
0136   process = TestProcess()
0137   process.foo = cms.EDProducer("FooProd")
0138   process.moduleToTest(process.foo)
0139   )_"
0140   };
0141 
0142   //setup data to pass
0143   auto barPutToken = config.produces<std::vector<Bar>>("bar");
0144   auto esPutToken = config.esProduces<FooData,FooRecord>();
0145 
0146   edm::test::TestProcessor tester{ config };
0147 
0148   //Run a test
0149   auto event = tester.test(std::make_pair(barPutToken, 
0150                                           std::make_unique<std::vector<Bar>(...)),
0151                            std::make_pair(esPutToken,
0152                                           std::make_unique<FooData>(...)));
0153   auto nMade = event.get<std::vector<Foo>>()->size();
0154   std::cout << nMade <<std::endl;
0155   
0156   if( nMade != ...) {
0157     return 1;
0158   }
0159   return 0;
0160 };
0161 ```
0162 
0163 ### Example using Catch2
0164 
0165 [Catch2](https://github.com/catchorg/Catch2/blob/master/docs/Readme.md#top) is a simple to use C++ unit testing framemwork. It can be used in conjuction with `TestProcessor` to drive a series of tests. In addition to the code, be sure to add `<use name="catch2"/>` to the `BuildFile.xml`.
0166 
0167 ```cpp
0168 #include "FWCore/TestProcessor/interface/TestProcessor.h"
0169 ...
0170 #include "catch.hpp"
0171 
0172 TEST_CASE("FooProd tests", "[FooProd]") {
0173   //The python configuration
0174   edm::test::TestProcessor::Config config{
0175 R"_(from FWCore.TestProcessor.TestProcess import *
0176 process = TestProcess()
0177 process.foo = cms.EDProducer("FooProd")
0178 process.moduleToTest(process.foo)
0179 )_"
0180   };
0181 
0182   //setup data to pass
0183   auto barPutToken = config.produces<std::vector<Bar>>("bar");
0184   auto esPutToken = config.esProduces<FooData,FooRecord>();
0185 
0186   SECTION("Pass standard data") {
0187     edm::test::TestProcessor tester{ config };
0188 
0189     //Run a test
0190     auto event = tester.test(std::make_pair(barPutToken, 
0191                                             std::make_unique<std::vector<Bar>(...)),
0192                              std::make_pair(esPutToken,
0193                                             std::make_unique<FooData>(...)));
0194     auto const& foos = event.get<std::vector<Foo>>();
0195     REQUIRE(foos->size() == ...);
0196     REQUIRE(foos[0] == Foo(...));
0197     ...
0198     
0199     SECTION("Move to new IOV") {
0200       tester.setRunNumber(2);
0201       auto event = tester.test(std::make_pair(barPutToken, 
0202                                               std::make_unique<std::vector<Bar>(...)),
0203                                std::make_pair(esPutToken,
0204                                               std::make_unique<FooData>(...)));
0205       auto const& foos = event.get<std::vector<Foo>>();
0206       REQUIRE(foos->size() == ...);
0207       REQUIRE(foos[0] == Foo(...));
0208       ...
0209     };
0210   };
0211   
0212   SECTION("Missing event data") {
0213     edm::test::TestProcessor tester{ config };
0214     REQUIRE_THROWS_AS(tester.test(std::make_pair(esPutToken,
0215                                               std::make_unique<FooData>(...))), 
0216                       cms::Exception);
0217   };
0218 }
0219 ```
0220 
0221 ## Autogenerating Tests
0222 
0223 Tests for new modules are automatically created when using `mkedprod`, `mkedfltr` or `mkedanlzr`. The same commands can be used to generate tests for existing modules just by running those commands from within the `test` directory of the package containing the module. For this case, you will need to manually add the following to `test/BuildFile.xml`:
0224 
0225 ```xml
0226 <bin file="test_catch2_*.cc" name="test<SubSystem name><Package Name>TP">
0227 <use name="FWCore/TestProcessor"/>
0228 <use name="catch2"/>
0229 </bin>
0230 ```
0231 
0232 
0233 ## Tips
0234 
0235 ### Testing different module configuration parameters
0236 
0237 It will often be the case that a group of tests only differ based on the values of parameters used to configure a module. For this case, one would like to have a _base configuration_ which is used as the starting point for each of tests. Rather than using string manipulation to create each new configuration, we can make use of the fact we will be running a python interpreter on each configuration.
0238 
0239 The _base configuration_ is a full configuration where, instead of setting specific values for each parameter that needs to be varied, we use a python variable name, where the variable is not declared in the _base configuration_.
0240 
0241 ```cpp
0242 const std::string baseConfig = 
0243 R"_(from FWCore.TestProcessor.TestProcess import *
0244 process = TestProcess()
0245 process.foo = cms.EDProducer('FooProd', value = cms.int32( fooValue ))
0246 process.moduleToTest(process.foo)
0247 )_";
0248 ```
0249 
0250 To generate a specific configuration, we just prepend to the `baseConfig` a string containing an expressiong which sets the values on the variable names
0251 
0252 ```cpp
0253 std::string fullConfig = "fooValue = 3\n"+baseConfig;
0254 ```
0255     
0256 Alternatively, you can setup default values in the base configuration
0257 
0258 ```cpp
0259 const std::string baseConfig = 
0260 R"_(from FWCore.TestProcessor.TestProcess import *
0261 process = TestProcess()
0262 process.foo = cms.EDProducer('FooProd', value = cms.int32( 1 ))
0263 process.moduleToTest(process.foo)
0264 )_";
0265 ```
0266 
0267 And then append a string at the end which sets the particular value
0268 
0269 ```cpp
0270 std::string fullConfig = baseConfig +
0271 R"_(process.foo.value = 3
0272 )_"
0273 ```
0274