1 | #ifndef BANDIT_OPTIONS_H |
2 | #define BANDIT_OPTIONS_H |
3 | |
4 | #include <algorithm> |
5 | #include <vector> |
6 | #include <iostream> |
7 | |
8 | #include <bandit/external/optionparser.h> |
9 | #include <bandit/filter_chain.h> |
10 | |
11 | namespace bandit { |
12 | namespace detail { |
13 | struct options { |
14 | template<typename ENUM> |
15 | struct argstr { |
16 | ENUM id; |
17 | std::string str; |
18 | }; |
19 | |
20 | // a vector of argstr that allows to iterate over the strings only |
21 | template<typename ENUM> |
22 | struct argstrs : std::vector<argstr<ENUM>> { |
23 | using std::vector<argstr<ENUM>>::vector; |
24 | |
25 | struct str_iterator |
26 | : public std::iterator<std::input_iterator_tag, std::string, int, const std::string*, std::string> { |
27 | using base_iterator = typename std::vector<argstr<ENUM>>::const_iterator; |
28 | |
29 | str_iterator() = delete; |
30 | |
31 | explicit str_iterator(base_iterator it) : it_(it) {} |
32 | |
33 | str_iterator& operator++() { |
34 | ++it_; |
35 | return *this; |
36 | } |
37 | |
38 | str_iterator operator++(int) { |
39 | str_iterator it(*this); |
40 | ++(*this); |
41 | return it; |
42 | } |
43 | |
44 | bool operator==(const str_iterator& other) const { |
45 | return it_ == other.it_; |
46 | } |
47 | |
48 | bool operator!=(const str_iterator& other) const { |
49 | return it_ != other.it_; |
50 | } |
51 | |
52 | reference operator*() const { |
53 | return it_->str; |
54 | } |
55 | |
56 | ENUM id() { |
57 | return it_->id; |
58 | } |
59 | |
60 | private: |
61 | base_iterator it_; |
62 | }; |
63 | |
64 | str_iterator strbegin() const { |
65 | return str_iterator(this->begin()); |
66 | }; |
67 | |
68 | str_iterator strend() const { |
69 | return str_iterator(this->end()); |
70 | }; |
71 | }; |
72 | |
73 | enum class formatters { |
74 | DEFAULT, |
75 | VS, |
76 | UNKNOWN |
77 | }; |
78 | |
79 | enum class reporters { |
80 | SINGLELINE, |
81 | XUNIT, |
82 | INFO, |
83 | SPEC, |
84 | DOTS, |
85 | CRASH, |
86 | UNKNOWN |
87 | }; |
88 | |
89 | struct argument : public option::Arg { |
90 | static const argstrs<reporters> reporter_list() { |
91 | return { |
92 | {reporters::CRASH, "crash" }, |
93 | {reporters::DOTS, "dots" }, |
94 | {reporters::SINGLELINE, "singleline" }, |
95 | {reporters::XUNIT, "xunit" }, |
96 | {reporters::INFO, "info" }, |
97 | {reporters::SPEC, "spec" }, |
98 | }; |
99 | } |
100 | |
101 | static const argstrs<formatters> formatter_list() { |
102 | return { |
103 | {formatters::DEFAULT, "default" }, |
104 | {formatters::VS, "vs" }, |
105 | }; |
106 | } |
107 | |
108 | template<typename ENUM> |
109 | static std::string comma_separated_list(argstrs<ENUM> list) { |
110 | std::string csl; |
111 | auto first = list.strbegin(); |
112 | if (first != list.strend()) { |
113 | csl += *first; |
114 | std::for_each(++first, list.strend(), [&](const std::string& elem) { |
115 | csl += ", " + elem; |
116 | }); |
117 | } |
118 | return csl; |
119 | } |
120 | |
121 | static std::string name(const option::Option& option) { |
122 | std::string copy(option.name); |
123 | return copy.substr(0, option.namelen); |
124 | } |
125 | |
126 | static option::ArgStatus Required(const option::Option& option, bool msg) { |
127 | if (option.arg != nullptr) { |
128 | return option::ARG_OK; |
129 | } |
130 | if (msg) { |
131 | std::cerr << "Option '" << name(option) << "' requires an argument\n" ; |
132 | } |
133 | return option::ARG_ILLEGAL; |
134 | } |
135 | |
136 | template<typename ENUM> |
137 | static option::ArgStatus OneOf(const option::Option& option, bool msg, const argstrs<ENUM>&& list) { |
138 | auto status = Required(option, msg); |
139 | if (status == option::ARG_OK && std::find(list.strbegin(), list.strend(), option.arg) == list.strend()) { |
140 | if (msg) { |
141 | std::cerr |
142 | << "Option argument of '" << name(option) << "' must be one of: " |
143 | << comma_separated_list(list) |
144 | << std::endl; |
145 | } |
146 | status = option::ARG_ILLEGAL; |
147 | } |
148 | return status; |
149 | } |
150 | |
151 | static option::ArgStatus Reporter(const option::Option& option, bool msg) { |
152 | return OneOf(option, msg, reporter_list()); |
153 | } |
154 | |
155 | static option::ArgStatus Formatter(const option::Option& option, bool msg) { |
156 | return OneOf(option, msg, formatter_list()); |
157 | } |
158 | }; |
159 | |
160 | options(int argc, char* argv[]) { |
161 | argc -= (argc > 0); |
162 | argv += (argc > 0); // Skip program name (argv[0]) if present |
163 | option::Stats stats(usage(), argc, argv); |
164 | options_.resize(stats.options_max); |
165 | std::vector<option::Option> buffer(stats.buffer_max); |
166 | option::Parser parse(usage(), argc, argv, options_.data(), buffer.data()); |
167 | parsed_ok_ = !parse.error(); |
168 | has_further_arguments_ = (parse.nonOptionsCount() != 0); |
169 | has_unknown_options_ = (options_[UNKNOWN] != nullptr); |
170 | |
171 | for (int i = 0; i < parse.optionsCount(); ++i) { |
172 | option::Option& opt = buffer[i]; |
173 | switch (opt.index()) { |
174 | case SKIP: |
175 | filter_chain_.push_back({opt.arg, true}); |
176 | break; |
177 | case ONLY: |
178 | filter_chain_.push_back({opt.arg, false}); |
179 | break; |
180 | } |
181 | } |
182 | } |
183 | |
184 | bool help() const { |
185 | return options_[HELP] != nullptr; |
186 | } |
187 | |
188 | bool parsed_ok() const { |
189 | return parsed_ok_; |
190 | } |
191 | |
192 | bool has_further_arguments() const { |
193 | return has_further_arguments_; |
194 | } |
195 | |
196 | bool has_unknown_options() const { |
197 | return has_unknown_options_; |
198 | } |
199 | |
200 | void print_usage() const { |
201 | option::printUsage(std::cout, usage()); |
202 | } |
203 | |
204 | bool version() const { |
205 | return options_[VERSION] != nullptr; |
206 | } |
207 | |
208 | reporters reporter() const { |
209 | return get_enumerator_from_string(argument::reporter_list(), options_[REPORTER].arg); |
210 | } |
211 | |
212 | bool no_color() const { |
213 | return options_[NO_COLOR] != nullptr; |
214 | } |
215 | |
216 | formatters formatter() const { |
217 | return get_enumerator_from_string(argument::formatter_list(), options_[FORMATTER].arg); |
218 | } |
219 | |
220 | const filter_chain_t& filter_chain() const { |
221 | return filter_chain_; |
222 | } |
223 | |
224 | bool break_on_failure() const { |
225 | return options_[BREAK_ON_FAILURE] != nullptr; |
226 | } |
227 | |
228 | bool dry_run() const { |
229 | return options_[DRY_RUN] != nullptr; |
230 | } |
231 | |
232 | private: |
233 | template<typename ENUM> |
234 | ENUM get_enumerator_from_string(const argstrs<ENUM>& list, const char* str) const { |
235 | if (str != nullptr) { |
236 | auto it = std::find(list.strbegin(), list.strend(), str); |
237 | if (it != list.strend()) { |
238 | return it.id(); |
239 | } |
240 | } |
241 | return ENUM::UNKNOWN; |
242 | } |
243 | |
244 | enum option_index { |
245 | UNKNOWN, |
246 | VERSION, |
247 | HELP, |
248 | REPORTER, |
249 | NO_COLOR, |
250 | FORMATTER, |
251 | SKIP, |
252 | ONLY, |
253 | BREAK_ON_FAILURE, |
254 | DRY_RUN, |
255 | }; |
256 | |
257 | template<typename ENUM> |
258 | static std::string append_list(std::string desc, argstrs<ENUM> list) { |
259 | return desc + ": " + argument::comma_separated_list(list); |
260 | } |
261 | |
262 | static const option::Descriptor* usage() { |
263 | static std::string reporter_help = append_list(" --reporter=<reporter>, " |
264 | "\tSelect reporter" , argument::reporter_list()); |
265 | static std::string formatter_help = append_list(" --formatter=<formatter>, " |
266 | "\tSelect error formatter" , argument::formatter_list()); |
267 | static const option::Descriptor usage[] = { |
268 | {UNKNOWN, 0, "" , "" , argument::None, |
269 | "USAGE: <executable> [options]\n\n" |
270 | "Options:" }, |
271 | {VERSION, 0, "" , "version" , argument::None, |
272 | " --version, \tPrint version of bandit" }, |
273 | {HELP, 0, "" , "help" , argument::None, |
274 | " --help, \tPrint usage and exit." }, |
275 | {REPORTER, 0, "" , "reporter" , argument::Reporter, reporter_help.c_str()}, |
276 | {NO_COLOR, 0, "" , "no-color" , argument::None, |
277 | " --no-color, \tSuppress colors in output" }, |
278 | {FORMATTER, 0, "" , "formatter" , argument::Formatter, formatter_help.c_str()}, |
279 | {SKIP, 0, "" , "skip" , argument::Required, |
280 | " --skip=<substring>, \tSkip all 'describe' and 'it' containing substring" }, |
281 | {ONLY, 0, "" , "only" , argument::Required, |
282 | " --only=<substring>, \tRun only 'describe' and 'it' containing substring" }, |
283 | {BREAK_ON_FAILURE, 0, "" , "break-on-failure" , argument::None, |
284 | " --break-on-failure, \tStop test run on first failing test" }, |
285 | {DRY_RUN, 0, "" , "dry-run" , argument::None, |
286 | " --dry-run, \tSkip all tests. Use to list available tests" }, |
287 | {0, 0, 0, 0, 0, 0}}; |
288 | |
289 | return usage; |
290 | } |
291 | |
292 | private: |
293 | std::vector<option::Option> options_; |
294 | filter_chain_t filter_chain_; |
295 | bool parsed_ok_; |
296 | bool has_further_arguments_; |
297 | bool has_unknown_options_; |
298 | }; |
299 | } |
300 | } |
301 | #endif |
302 | |