xref: /freebsd/contrib/kyua/utils/cmdline/ui_test.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
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