1 // Copyright 2011 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 // may be used to endorse or promote products derived from this software
15 // without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 #include "utils/cmdline/ui.hpp"
30
31 #if defined(HAVE_CONFIG_H)
32 # include "config.h"
33 #endif
34
35 extern "C" {
36 #include <sys/param.h>
37 #include <sys/ioctl.h>
38
39 #include <fcntl.h>
40 #if defined(HAVE_TERMIOS_H)
41 # include <termios.h>
42 #endif
43 #include <unistd.h>
44 }
45
46 #include <cerrno>
47 #include <cstring>
48
49 #include <atf-c++.hpp>
50
51 #include "utils/cmdline/globals.hpp"
52 #include "utils/cmdline/ui_mock.hpp"
53 #include "utils/env.hpp"
54 #include "utils/format/macros.hpp"
55 #include "utils/optional.ipp"
56 #include "utils/text/table.hpp"
57
58 namespace cmdline = utils::cmdline;
59 namespace text = utils::text;
60
61 using utils::none;
62 using utils::optional;
63
64
65 namespace {
66
67
68 /// Reopens stdout as a tty and returns its width.
69 ///
70 /// \return The width of the tty in columns. If the width is wider than 80, the
71 /// result is 5 columns narrower to match the screen_width() algorithm.
72 static std::size_t
reopen_stdout(void)73 reopen_stdout(void)
74 {
75 const int fd = ::open("/dev/tty", O_WRONLY);
76 if (fd == -1)
77 ATF_SKIP(F("Cannot open tty for test: %s") % ::strerror(errno));
78 struct ::winsize ws;
79 if (::ioctl(fd, TIOCGWINSZ, &ws) == -1)
80 ATF_SKIP(F("Cannot determine size of tty: %s") % ::strerror(errno));
81
82 if (fd != STDOUT_FILENO) {
83 if (::dup2(fd, STDOUT_FILENO) == -1)
84 ATF_SKIP(F("Failed to redirect stdout: %s") % ::strerror(errno));
85 ::close(fd);
86 }
87
88 return ws.ws_col >= 80 ? ws.ws_col - 5 : ws.ws_col;
89 }
90
91
92 } // anonymous namespace
93
94
95 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__no_tty);
ATF_TEST_CASE_BODY(ui__screen_width__columns_set__no_tty)96 ATF_TEST_CASE_BODY(ui__screen_width__columns_set__no_tty)
97 {
98 utils::setenv("COLUMNS", "4321");
99 ::close(STDOUT_FILENO);
100
101 cmdline::ui ui;
102 ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
103 }
104
105
106 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__tty);
ATF_TEST_CASE_BODY(ui__screen_width__columns_set__tty)107 ATF_TEST_CASE_BODY(ui__screen_width__columns_set__tty)
108 {
109 utils::setenv("COLUMNS", "4321");
110 (void)reopen_stdout();
111
112 cmdline::ui ui;
113 ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
114 }
115
116
117 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__no_tty);
ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__no_tty)118 ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__no_tty)
119 {
120 utils::setenv("COLUMNS", "");
121 ::close(STDOUT_FILENO);
122
123 cmdline::ui ui;
124 ATF_REQUIRE(!ui.screen_width());
125 }
126
127
128 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__tty);
ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__tty)129 ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__tty)
130 {
131 utils::setenv("COLUMNS", "");
132 const std::size_t columns = reopen_stdout();
133
134 cmdline::ui ui;
135 ATF_REQUIRE_EQ(columns, ui.screen_width().get());
136 }
137
138
139 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__no_tty);
ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__no_tty)140 ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__no_tty)
141 {
142 utils::setenv("COLUMNS", "foo bar");
143 ::close(STDOUT_FILENO);
144
145 cmdline::ui ui;
146 ATF_REQUIRE(!ui.screen_width());
147 }
148
149
150 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__tty);
ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__tty)151 ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__tty)
152 {
153 utils::setenv("COLUMNS", "foo bar");
154 const std::size_t columns = reopen_stdout();
155
156 cmdline::ui ui;
157 ATF_REQUIRE_EQ(columns, ui.screen_width().get());
158 }
159
160
161 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__tty_is_file);
ATF_TEST_CASE_BODY(ui__screen_width__tty_is_file)162 ATF_TEST_CASE_BODY(ui__screen_width__tty_is_file)
163 {
164 utils::unsetenv("COLUMNS");
165 const int fd = ::open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0755);
166 ATF_REQUIRE(fd != -1);
167 if (fd != STDOUT_FILENO) {
168 ATF_REQUIRE(::dup2(fd, STDOUT_FILENO) != -1);
169 ::close(fd);
170 }
171
172 cmdline::ui ui;
173 ATF_REQUIRE(!ui.screen_width());
174 }
175
176
177 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__cached);
ATF_TEST_CASE_BODY(ui__screen_width__cached)178 ATF_TEST_CASE_BODY(ui__screen_width__cached)
179 {
180 cmdline::ui ui;
181
182 utils::setenv("COLUMNS", "100");
183 ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
184
185 utils::setenv("COLUMNS", "80");
186 ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
187
188 utils::unsetenv("COLUMNS");
189 ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
190 }
191
192
193 ATF_TEST_CASE_WITHOUT_HEAD(ui__err);
ATF_TEST_CASE_BODY(ui__err)194 ATF_TEST_CASE_BODY(ui__err)
195 {
196 cmdline::ui_mock ui(10); // Keep shorter than message.
197 ui.err("This is a short message");
198 ATF_REQUIRE_EQ(1, ui.err_log().size());
199 ATF_REQUIRE_EQ("This is a short message", ui.err_log()[0]);
200 ATF_REQUIRE(ui.out_log().empty());
201 }
202
203
204 ATF_TEST_CASE_WITHOUT_HEAD(ui__err__tolerates_newline);
ATF_TEST_CASE_BODY(ui__err__tolerates_newline)205 ATF_TEST_CASE_BODY(ui__err__tolerates_newline)
206 {
207 cmdline::ui_mock ui(10); // Keep shorter than message.
208 ui.err("This is a short message\n");
209 ATF_REQUIRE_EQ(1, ui.err_log().size());
210 ATF_REQUIRE_EQ("This is a short message\n", ui.err_log()[0]);
211 ATF_REQUIRE(ui.out_log().empty());
212 }
213
214
215 ATF_TEST_CASE_WITHOUT_HEAD(ui__out);
ATF_TEST_CASE_BODY(ui__out)216 ATF_TEST_CASE_BODY(ui__out)
217 {
218 cmdline::ui_mock ui(10); // Keep shorter than message.
219 ui.out("This is a short message");
220 ATF_REQUIRE(ui.err_log().empty());
221 ATF_REQUIRE_EQ(1, ui.out_log().size());
222 ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
223 }
224
225
226 ATF_TEST_CASE_WITHOUT_HEAD(ui__out__tolerates_newline);
ATF_TEST_CASE_BODY(ui__out__tolerates_newline)227 ATF_TEST_CASE_BODY(ui__out__tolerates_newline)
228 {
229 cmdline::ui_mock ui(10); // Keep shorter than message.
230 ui.out("This is a short message\n");
231 ATF_REQUIRE(ui.err_log().empty());
232 ATF_REQUIRE_EQ(1, ui.out_log().size());
233 ATF_REQUIRE_EQ("This is a short message\n", ui.out_log()[0]);
234 }
235
236
237 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__no_refill);
ATF_TEST_CASE_BODY(ui__out_wrap__no_refill)238 ATF_TEST_CASE_BODY(ui__out_wrap__no_refill)
239 {
240 cmdline::ui_mock ui(100);
241 ui.out_wrap("This is a short message");
242 ATF_REQUIRE(ui.err_log().empty());
243 ATF_REQUIRE_EQ(1, ui.out_log().size());
244 ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
245 }
246
247
248 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__refill);
ATF_TEST_CASE_BODY(ui__out_wrap__refill)249 ATF_TEST_CASE_BODY(ui__out_wrap__refill)
250 {
251 cmdline::ui_mock ui(16);
252 ui.out_wrap("This is a short message");
253 ATF_REQUIRE(ui.err_log().empty());
254 ATF_REQUIRE_EQ(2, ui.out_log().size());
255 ATF_REQUIRE_EQ("This is a short", ui.out_log()[0]);
256 ATF_REQUIRE_EQ("message", ui.out_log()[1]);
257 }
258
259
260 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__no_refill);
ATF_TEST_CASE_BODY(ui__out_tag_wrap__no_refill)261 ATF_TEST_CASE_BODY(ui__out_tag_wrap__no_refill)
262 {
263 cmdline::ui_mock ui(100);
264 ui.out_tag_wrap("Some long tag: ", "This is a short message");
265 ATF_REQUIRE(ui.err_log().empty());
266 ATF_REQUIRE_EQ(1, ui.out_log().size());
267 ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
268 }
269
270
271 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__repeat);
ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__repeat)272 ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__repeat)
273 {
274 cmdline::ui_mock ui(32);
275 ui.out_tag_wrap("Some long tag: ", "This is a short message");
276 ATF_REQUIRE(ui.err_log().empty());
277 ATF_REQUIRE_EQ(2, ui.out_log().size());
278 ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
279 ATF_REQUIRE_EQ("Some long tag: message", ui.out_log()[1]);
280 }
281
282
283 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__no_repeat);
ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__no_repeat)284 ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__no_repeat)
285 {
286 cmdline::ui_mock ui(32);
287 ui.out_tag_wrap("Some long tag: ", "This is a short message", false);
288 ATF_REQUIRE(ui.err_log().empty());
289 ATF_REQUIRE_EQ(2, ui.out_log().size());
290 ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
291 ATF_REQUIRE_EQ(" message", ui.out_log()[1]);
292 }
293
294
295 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__tag_too_long);
ATF_TEST_CASE_BODY(ui__out_tag_wrap__tag_too_long)296 ATF_TEST_CASE_BODY(ui__out_tag_wrap__tag_too_long)
297 {
298 cmdline::ui_mock ui(5);
299 ui.out_tag_wrap("Some long tag: ", "This is a short message");
300 ATF_REQUIRE(ui.err_log().empty());
301 ATF_REQUIRE_EQ(1, ui.out_log().size());
302 ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
303 }
304
305
306 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__empty);
ATF_TEST_CASE_BODY(ui__out_table__empty)307 ATF_TEST_CASE_BODY(ui__out_table__empty)
308 {
309 const text::table table(3);
310
311 text::table_formatter formatter;
312 formatter.set_separator(" | ");
313 formatter.set_column_width(0, 23);
314 formatter.set_column_width(1, text::table_formatter::width_refill);
315
316 cmdline::ui_mock ui(52);
317 ui.out_table(table, formatter, " ");
318 ATF_REQUIRE(ui.out_log().empty());
319 }
320
321
322 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__not_empty);
ATF_TEST_CASE_BODY(ui__out_table__not_empty)323 ATF_TEST_CASE_BODY(ui__out_table__not_empty)
324 {
325 text::table table(3);
326 {
327 text::table_row row;
328 row.push_back("First");
329 row.push_back("Second");
330 row.push_back("Third");
331 table.add_row(row);
332 }
333 {
334 text::table_row row;
335 row.push_back("Fourth with some text");
336 row.push_back("Fifth with some more text");
337 row.push_back("Sixth foo");
338 table.add_row(row);
339 }
340
341 text::table_formatter formatter;
342 formatter.set_separator(" | ");
343 formatter.set_column_width(0, 23);
344 formatter.set_column_width(1, text::table_formatter::width_refill);
345
346 cmdline::ui_mock ui(52);
347 ui.out_table(table, formatter, " ");
348 ATF_REQUIRE_EQ(4, ui.out_log().size());
349 ATF_REQUIRE_EQ(" First | Second | Third",
350 ui.out_log()[0]);
351 ATF_REQUIRE_EQ(" Fourth with some text | Fifth with | Sixth foo",
352 ui.out_log()[1]);
353 ATF_REQUIRE_EQ(" | some more | ",
354 ui.out_log()[2]);
355 ATF_REQUIRE_EQ(" | text | ",
356 ui.out_log()[3]);
357 }
358
359
360 ATF_TEST_CASE_WITHOUT_HEAD(print_error);
ATF_TEST_CASE_BODY(print_error)361 ATF_TEST_CASE_BODY(print_error)
362 {
363 cmdline::init("error-program");
364 cmdline::ui_mock ui;
365 cmdline::print_error(&ui, "The error.");
366 ATF_REQUIRE(ui.out_log().empty());
367 ATF_REQUIRE_EQ(1, ui.err_log().size());
368 ATF_REQUIRE_EQ("error-program: E: The error.", ui.err_log()[0]);
369 }
370
371
372 ATF_TEST_CASE_WITHOUT_HEAD(print_info);
ATF_TEST_CASE_BODY(print_info)373 ATF_TEST_CASE_BODY(print_info)
374 {
375 cmdline::init("info-program");
376 cmdline::ui_mock ui;
377 cmdline::print_info(&ui, "The info.");
378 ATF_REQUIRE(ui.out_log().empty());
379 ATF_REQUIRE_EQ(1, ui.err_log().size());
380 ATF_REQUIRE_EQ("info-program: I: The info.", ui.err_log()[0]);
381 }
382
383
384 ATF_TEST_CASE_WITHOUT_HEAD(print_warning);
ATF_TEST_CASE_BODY(print_warning)385 ATF_TEST_CASE_BODY(print_warning)
386 {
387 cmdline::init("warning-program");
388 cmdline::ui_mock ui;
389 cmdline::print_warning(&ui, "The warning.");
390 ATF_REQUIRE(ui.out_log().empty());
391 ATF_REQUIRE_EQ(1, ui.err_log().size());
392 ATF_REQUIRE_EQ("warning-program: W: The warning.", ui.err_log()[0]);
393 }
394
395
ATF_INIT_TEST_CASES(tcs)396 ATF_INIT_TEST_CASES(tcs)
397 {
398 ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__no_tty);
399 ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__tty);
400 ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__no_tty);
401 ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__tty);
402 ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__no_tty);
403 ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__tty);
404 ATF_ADD_TEST_CASE(tcs, ui__screen_width__tty_is_file);
405 ATF_ADD_TEST_CASE(tcs, ui__screen_width__cached);
406
407 ATF_ADD_TEST_CASE(tcs, ui__err);
408 ATF_ADD_TEST_CASE(tcs, ui__err__tolerates_newline);
409 ATF_ADD_TEST_CASE(tcs, ui__out);
410 ATF_ADD_TEST_CASE(tcs, ui__out__tolerates_newline);
411
412 ATF_ADD_TEST_CASE(tcs, ui__out_wrap__no_refill);
413 ATF_ADD_TEST_CASE(tcs, ui__out_wrap__refill);
414 ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__no_refill);
415 ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__repeat);
416 ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__no_repeat);
417 ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__tag_too_long);
418 ATF_ADD_TEST_CASE(tcs, ui__out_table__empty);
419 ATF_ADD_TEST_CASE(tcs, ui__out_table__not_empty);
420
421 ATF_ADD_TEST_CASE(tcs, print_error);
422 ATF_ADD_TEST_CASE(tcs, print_info);
423 ATF_ADD_TEST_CASE(tcs, print_warning);
424 }
425