Project
Loading...
Searching...
No Matches
test_DeviceSpecHelpers.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#include "Mocking.h"
17#include "../src/DeviceSpecHelpers.h"
18#include <catch_amalgamated.hpp>
19#include <sstream>
20#include <cstring>
21#include <vector>
22#include <map>
23#include <cstring>
24#include "../src/SimpleResourceManager.h"
25#include "../src/ComputingResourceHelpers.h"
26
27namespace o2::framework
28{
29
30using CheckMatrix = std::map<std::string, std::vector<std::pair<std::string, std::string>>>;
31
32// search for an option in the device execution
33bool search(DeviceExecution const& execution, std::string const& option, std::string const& argument)
34{
35 bool foundOption = false;
36 for (auto const& execArg : execution.args) {
37 if (execArg == nullptr) {
38 break;
39 }
40 if (!foundOption) {
41 foundOption = option == execArg;
42 } else if (argument == execArg) {
43 return true;
44 } else {
45 // the required argument to the option is not found
46 foundOption = false;
47 }
48 }
49 return false;
50}
51
52// create the device execution from the device specs and process the command line arguments
53// the check matrix contains a map of options to be founf per processor
54void check(const std::vector<std::string>& arguments,
55 const std::vector<ConfigParamSpec>& workflowOptions,
56 const std::vector<DeviceSpec>& deviceSpecs,
57 CheckMatrix& matrix)
58{
59 std::stringstream output;
60 for (auto const& arg : arguments) {
61 output << " " << arg;
62 }
63
64 std::vector<DeviceExecution> deviceExecutions(deviceSpecs.size());
65 std::vector<DeviceControl> deviceControls(deviceSpecs.size());
66 std::vector<DataProcessorInfo> dataProcessorInfos;
67 for (auto& [name, _] : matrix) {
68 dataProcessorInfos.push_back(DataProcessorInfo{
69 .name = name,
70 .executable = "executable-name",
71 .cmdLineArgs = arguments,
72 .workflowOptions = workflowOptions,
73 });
74 }
75 DriverConfig driverConfig{};
76 DeviceSpecHelpers::prepareArguments(true, true, false, 8080,
77 driverConfig,
78 dataProcessorInfos,
79 deviceSpecs,
80 deviceExecutions,
81 deviceControls, {},
82 "workflow-id");
83
84 for (size_t index = 0; index < deviceSpecs.size(); index++) {
85 const auto& deviceSpec = deviceSpecs[index];
86 const auto& deviceExecution = deviceExecutions[index];
87 std::stringstream execArgs;
88 for (const auto& arg : deviceExecution.args) {
89 if (arg == nullptr) {
90 // the nullptr terminates the argument list
91 break;
92 }
93 execArgs << " " << arg;
94 }
95 for (auto const& testCase : matrix[deviceSpec.name]) {
96 INFO(std::string("can not find option: ") + testCase.first + " " + testCase.second);
97 REQUIRE(search(deviceExecution, testCase.first, testCase.second));
98 }
99 }
100}
101
102TEST_CASE("test_prepareArguments")
103{
104 int lm[1][2] = {{1, 2}};
105 std::vector<ConfigParamSpec> workflowOptions{
106 {"foo", VariantType::String, "bar", {"charlie foxtrot"}},
107 {"depth", VariantType::Int, 1, {"height"}},
108 {"labeled", VariantType::LabeledArrayInt, LabeledArray<int>{&lm[0][0], 1, 2, {"r"}, {"c1", "c2"}}, {"a labeled array"}},
109 };
110 std::string labeledDefault = R"({"labels_rows":["r"],"labels_cols":["c1","c2"],"values":[[1,2]]})";
111 std::string labeledOther = R"({"labels_rows":["r"],"labels_cols":["c1","c2"],"values":[[3,4]]})";
112
113 auto algorithm = [](ProcessingContext& /*ctx*/) {};
114
115 WorkflowSpec workflow{
116 {"processor0",
117 {},
118 {OutputSpec{{"output"}, "TST", "DUMMYDATA", 0, Lifetime::Timeframe}},
119 AlgorithmSpec(algorithm),
120 Options{
121 {"mode", VariantType::String, "default", {"The Mode"}},
122 }},
123 {"processor1",
124 {InputSpec{"input", "TST", "DUMMYDATA", 0, Lifetime::Timeframe}},
125 {},
126 AlgorithmSpec(algorithm),
127 Options{
128 {"mode", VariantType::String, "default", {"The Mode"}},
129 }},
130 };
131
132 std::vector<DeviceSpec> deviceSpecs;
133
134 std::vector<ComputingResource> resources = {ComputingResourceHelpers::getLocalhostResource()};
135 auto rm = std::make_unique<SimpleResourceManager>(resources);
136
137 auto configContext = makeEmptyConfigContext();
138 auto channelPolicies = ChannelConfigurationPolicy::createDefaultPolicies(*configContext);
139 auto callbacksPolicies = CallbacksPolicy::createDefaultPolicies();
140
142 channelPolicies,
144 callbacksPolicies,
145 deviceSpecs,
146 *rm, "workflow-id", *configContext);
147
148 // Now doing the test cases
149 CheckMatrix matrix;
150
151 // checking with empty command line arguments, all processors must have the options with
152 // default arguments
153 matrix["processor0"] = {{"--depth", "1"}, {"--foo", "bar"}, {"--mode", "default"}};
154 matrix["processor1"] = matrix["processor0"];
155 check({}, workflowOptions, deviceSpecs, matrix);
156
157 // checking with unknown arguments, silently ignored, same test matrix
158 check({"--unknown", "option"}, workflowOptions, deviceSpecs, matrix);
159
160 // configuring mode, both devices must have the option set
161 matrix["processor0"] = {{"--depth", "1"}, {"--foo", "bar"}, {"--mode", "silly"}};
162 matrix["processor1"] = matrix["processor0"];
163 check({"--mode", "silly"}, workflowOptions, deviceSpecs, matrix);
164
165 // configuring option group, only processor0 must have the option set, processor1 default
166 matrix["processor0"] = {{"--depth", "1"}, {"--foo", "bar"}, {"--mode", "silly"}};
167 matrix["processor1"] = {{"--depth", "1"}, {"--foo", "bar"}, {"--mode", "default"}};
168 check({"--processor0", "--mode silly"}, workflowOptions, deviceSpecs, matrix);
169
170 // processor0 must have the mode set to silly via option group, processor1 advanced from the argument
171 matrix["processor0"] = {{"--depth", "1"}, {"--foo", "bar"}, {"--mode", "silly"}};
172 matrix["processor1"] = {{"--depth", "1"}, {"--foo", "bar"}, {"--mode", "advanced"}};
173 check({"--mode", "advanced", "--processor0", "--mode silly"}, workflowOptions, deviceSpecs, matrix);
174
175 // both devices have the workflow option propagated, others defaulted
176 matrix["processor0"] = {{"--depth", "2"}, {"--foo", "bar"}, {"--mode", "default"}};
177 matrix["processor1"] = matrix["processor0"];
178 check({"--depth", "2"}, workflowOptions, deviceSpecs, matrix);
179
180 // both devices have the workflow option propagated, processor0 mode silly via option group
181 matrix["processor0"] = {{"--depth", "2"}, {"--foo", "bar"}, {"--mode", "silly"}};
182 matrix["processor1"] = {{"--depth", "2"}, {"--foo", "bar"}, {"--mode", "default"}};
183 check({"--depth", "2", "--processor0", "--mode silly"}, workflowOptions, deviceSpecs, matrix);
184
185 // a LabeledArray workflow option is forwarded as a single JSON argument, defaulted or set
186 matrix["processor0"] = {{"--depth", "1"}, {"--labeled", labeledDefault}};
187 matrix["processor1"] = matrix["processor0"];
188 check({}, workflowOptions, deviceSpecs, matrix);
189 matrix["processor0"] = {{"--depth", "1"}, {"--labeled", labeledOther}};
190 matrix["processor1"] = matrix["processor0"];
191 check({"--labeled", labeledOther}, workflowOptions, deviceSpecs, matrix);
192}
193
194TEST_CASE("CheckOptionReworking")
195{
196 {
197 std::vector<DataProcessorInfo> infos = {
198 {.cmdLineArgs = {"--driver-client-backend", "foo"}},
199 {}};
200 DeviceSpecHelpers::reworkHomogeneousOption(infos, "--driver-client-backend", "stdout://");
201 REQUIRE(infos[0].cmdLineArgs[1] == "foo");
202 REQUIRE(infos[1].cmdLineArgs[1] == "foo");
203 }
204 {
205 std::vector<DataProcessorInfo> infos = {
206 {.cmdLineArgs = {"--driver-client-backend", "foo"}},
207 {.cmdLineArgs = {"--driver-client-backend", "bar"}}};
208 REQUIRE_THROWS_AS(DeviceSpecHelpers::reworkHomogeneousOption(infos, "--driver-client-backend", "stdout://"), o2::framework::RuntimeErrorRef);
209 }
210 {
211 std::vector<DataProcessorInfo> infos = {
212 {.cmdLineArgs = {"--driver-client-backend", "foo"}},
213 {.cmdLineArgs = {"--driver-client-backend", "foo"}}};
214 DeviceSpecHelpers::reworkHomogeneousOption(infos, "--driver-client-backend", "stdout://");
215 REQUIRE(infos[0].cmdLineArgs[1] == "foo");
216 REQUIRE(infos[1].cmdLineArgs[1] == "foo");
217 }
218 {
219 std::vector<DataProcessorInfo> infos = {
220 {.cmdLineArgs = {"foo", "bar"}},
221 {.cmdLineArgs = {"fnjcnak", "foo"}}};
222 DeviceSpecHelpers::reworkHomogeneousOption(infos, "--driver-client-backend", "stdout://");
223 REQUIRE(infos[0].cmdLineArgs[3] == "stdout://");
224 REQUIRE(infos[1].cmdLineArgs[3] == "stdout://");
225 }
226 {
227 std::vector<DataProcessorInfo> infos = {
228 {.cmdLineArgs = {"foo", "bar", "--driver-client-backend", "bar"}},
229 {.cmdLineArgs = {"fnjcnak", "foo"}}};
230 DeviceSpecHelpers::reworkHomogeneousOption(infos, "--driver-client-backend", "stdout://");
231 REQUIRE(infos[0].cmdLineArgs[3] == "bar");
232 REQUIRE(infos[1].cmdLineArgs[3] == "bar");
233 }
234}
235
236TEST_CASE("CheckIntegerReworking")
237{
238 {
239 std::vector<DataProcessorInfo> infos = {
240 {{}, {}, {"--readers", "2"}},
241 {}};
243 infos, "--readers", nullptr, 1, [](long long x, long long y) { return x > y ? x : y; });
244 REQUIRE(infos[0].cmdLineArgs[1] == "2");
245 REQUIRE(infos[1].cmdLineArgs[1] == "2");
246 }
247 {
248 std::vector<DataProcessorInfo> infos = {
249 {},
250 {}};
252 infos, "--readers", nullptr, 1, [](long long x, long long y) { return x > y ? x : y; });
253 REQUIRE(infos[0].cmdLineArgs.size() == 0);
254 REQUIRE(infos[1].cmdLineArgs.size() == 0);
255 }
256 {
257 std::vector<DataProcessorInfo> infos = {
258 {},
259 {}};
261 infos, "--readers", []() { return 1; }, 3, [](long long x, long long y) { return x > y ? x : y; });
262 REQUIRE(infos[0].cmdLineArgs.size() == 2);
263 REQUIRE(infos[1].cmdLineArgs.size() == 2);
264 REQUIRE(infos[0].cmdLineArgs[1] == "1");
265 REQUIRE(infos[1].cmdLineArgs[1] == "1");
266 }
267 {
268 std::vector<DataProcessorInfo> infos = {
269 {{}, {}, {"--readers", "2"}},
270 {{}, {}, {"--readers", "3"}}};
272 infos, "--readers", []() { return 1; }, 1, [](long long x, long long y) { return x > y ? x : y; });
273 REQUIRE(infos[0].cmdLineArgs.size() == 2);
274 REQUIRE(infos[1].cmdLineArgs.size() == 2);
275 REQUIRE(infos[0].cmdLineArgs[1] == "3");
276 REQUIRE(infos[1].cmdLineArgs[1] == "3");
277 }
278 {
279 std::vector<DataProcessorInfo> infos = {
280 {{}, {}, {"--readers", "3"}},
281 {{}, {}, {"--readers", "2"}}};
283 infos, "--readers", []() { return 1; }, 1, [](long long x, long long y) { return x > y ? x : y; });
284 REQUIRE(infos[0].cmdLineArgs.size() == 2);
285 REQUIRE(infos[1].cmdLineArgs.size() == 2);
286 REQUIRE(infos[0].cmdLineArgs[1] == "3");
287 REQUIRE(infos[1].cmdLineArgs[1] == "3");
288 }
289 {
290 std::vector<DataProcessorInfo> infos = {
291 {.cmdLineArgs = {"foo", "bar", "--readers", "3"}},
292 {.cmdLineArgs = {"--readers", "2"}}};
294 infos, "--readers", []() { return 1; }, 1, [](long long x, long long y) { return x > y ? x : y; });
295 REQUIRE(infos[0].cmdLineArgs.size() == 4);
296 REQUIRE(infos[1].cmdLineArgs.size() == 2);
297 REQUIRE(infos[0].cmdLineArgs[3] == "3");
298 REQUIRE(infos[1].cmdLineArgs[1] == "3");
299 }
300}
301
302TEST_CASE("Check validity of the workflow")
303{
304 WorkflowSpec workflow1{
305 {
306 .name = "processor0",
307 .outputs = {OutputSpec{{"output"}, "TST", "DUMMYDATA", 0, Lifetime::Timeframe}},
308 },
309 {
310 .name = "processor1",
311 .inputs = {InputSpec{"input", "TST", "DUMMYDATA", 0, Lifetime::Timeframe}},
312 },
313 };
314 REQUIRE_NOTHROW(DeviceSpecHelpers::validate(workflow1));
315
316 WorkflowSpec workflow2{
317 {
318 .name = "processor0",
319 .outputs = {OutputSpec{{"output"}, "TST", "DUMMYDATA", 0, Lifetime::Timeframe},
320 OutputSpec{{"output2"}, "TST", "DUMMYDATA", 0, Lifetime::Timeframe}},
321 },
322 {
323 .name = "processor1",
324 .inputs = {InputSpec{"input", "TST", "DUMMYDATA", 0, Lifetime::Timeframe}},
325 },
326 };
327 REQUIRE_THROWS(DeviceSpecHelpers::validate(workflow2));
328
329 WorkflowSpec workflow3{
330 {
331 .name = "processor0",
332 .outputs = {OutputSpec{{"output"}, "TST", "DUMMYDATA", 0, Lifetime::Timeframe},
333 OutputSpec{{"output2"}, "TST", "DUMMYDATA", 2, Lifetime::Sporadic}},
334 },
335 {
336 .name = "processor1",
337 .outputs = {OutputSpec{{"output"}, "TST", "DUMMYDATA", 1, Lifetime::Timeframe},
338 OutputSpec{{"output2"}, "TST", "DUMMYDATA", 2, Lifetime::Sporadic}},
339 },
340 {
341 .name = "processor2",
342 .inputs = {InputSpec{"input", "TST", "DUMMYDATA", 2, Lifetime::Timeframe}},
343 },
344 };
345 REQUIRE_NOTHROW(DeviceSpecHelpers::validate(workflow3));
346
347 WorkflowSpec workflow4{
348 {
349 .name = "processor0",
350 .outputs = {OutputSpec{{"output"}, "TST", "DUMMYDATA", 0, Lifetime::Timeframe},
351 OutputSpec{{"output2"}, "TST", "DUMMYDATA", 2, Lifetime::Sporadic}},
352 },
353 {
354 .name = "processor1",
355 .outputs = {OutputSpec{{"output"}, "TST", "DUMMYDATA", 0, Lifetime::Timeframe},
356 OutputSpec{{"output2"}, "TST", "DUMMYDATA", 2, Lifetime::Sporadic}},
357 },
358 {
359 .name = "processor2",
360 .inputs = {InputSpec{"input", "TST", "DUMMYDATA", 2, Lifetime::Timeframe}},
361 },
362 };
363 REQUIRE_THROWS(DeviceSpecHelpers::validate(workflow4));
364}
365
366TEST_CASE("CheckReworkingEnv")
367{
368 DeviceSpec spec{.inputTimesliceId = 1};
369 std::string env = "FOO={timeslice0}";
370 REQUIRE(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec) == "FOO=1");
371 env = "FOO={timeslice0} BAR={timeslice4}";
372 REQUIRE(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec) == "FOO=1 BAR=5");
373 env = "FOO={timeslice0} BAR={timeslice4} BAZ={timeslice5}";
374 REQUIRE(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec) == "FOO=1 BAR=5 BAZ=6");
375 env = "";
376 REQUIRE(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec) == "");
377 env = "Plottigat";
378 REQUIRE(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec) == "Plottigat");
379 env = "{timeslice}";
380 REQUIRE(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec) == "{timeslice}");
381 env = "{timeslicepluto";
382 REQUIRE(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec) == "{timeslicepluto");
383}
384
385} // namespace o2::framework
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
std::unique_ptr< ConfigContext > makeEmptyConfigContext()
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLint y
Definition glcorearb.h:270
bpo::variables_map arguments
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
TEST_CASE("test_prepareArguments")
bool search(DeviceExecution const &execution, std::string const &option, std::string const &argument)
std::vector< DataProcessorSpec > WorkflowSpec
std::map< std::string, std::vector< std::pair< std::string, std::string > > > CheckMatrix
static std::vector< CallbacksPolicy > createDefaultPolicies()
static std::vector< ChannelConfigurationPolicy > createDefaultPolicies(ConfigContext const &configContext)
Default policies to use, based on the contents of the @configContex content.
static std::vector< CompletionPolicy > createDefaultPolicies()
Helper to create the default configuration.
std::string name
Name of the associated DataProcessorSpec.
std::vector< char * > args
The options passed to a given device.
static void validate(WorkflowSpec const &workflow)
static std::string reworkTimeslicePlaceholder(std::string const &str, DeviceSpec const &spec)
static void prepareArguments(bool defaultQuiet, bool defaultStopped, bool intereactive, unsigned short driverPort, DriverConfig const &driverConfig, std::vector< DataProcessorInfo > const &processorInfos, std::vector< DeviceSpec > const &deviceSpecs, std::vector< DeviceExecution > &deviceExecutions, std::vector< DeviceControl > &deviceControls, std::vector< ConfigParamSpec > const &detectedOptions, std::string const &uniqueWorkflowId)
static void reworkHomogeneousOption(std::vector< DataProcessorInfo > &infos, char const *name, char const *defaultValue)
static void reworkIntegerOption(std::vector< DataProcessorInfo > &infos, char const *name, std::function< long long()> defaultValueCallback, long long startValue, std::function< long long(long long, long long)> bestValue)
static void dataProcessorSpecs2DeviceSpecs(const WorkflowSpec &workflow, std::vector< ChannelConfigurationPolicy > const &channelPolicies, std::vector< CompletionPolicy > const &completionPolicies, std::vector< DispatchPolicy > const &dispatchPolicies, std::vector< ResourcePolicy > const &resourcePolicies, std::vector< CallbacksPolicy > const &callbacksPolicies, std::vector< SendingPolicy > const &sendingPolicy, std::vector< ForwardingPolicy > const &forwardingPolicies, std::vector< DeviceSpec > &devices, ResourceManager &resourceManager, std::string const &uniqueWorkflowId, ConfigContext const &configContext, bool optimizeTopology=false, unsigned short resourcesMonitoringInterval=0, std::string const &channelPrefix="", OverrideServiceSpecs const &overrideServices={})
size_t inputTimesliceId
The time pipelining id of this particular device.
Definition DeviceSpec.h:68