xref: /netbsd-src/external/bsd/kyua-cli/dist/utils/cmdline/ui_test.cpp (revision 76c7fc5f6b13ed0b1508e6b313e88e59977ed78e)
1 // Copyright 2011 Google Inc.
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 extern "C" {
32 #include <sys/ioctl.h>
33 
34 #include <fcntl.h>
35 #include <unistd.h>
36 }
37 
38 #include <cerrno>
39 #include <cstring>
40 
41 #include <atf-c++.hpp>
42 
43 #include "utils/cmdline/globals.hpp"
44 #include "utils/cmdline/ui_mock.hpp"
45 #include "utils/env.hpp"
46 #include "utils/format/macros.hpp"
47 #include "utils/optional.ipp"
48 #include "utils/text/table.hpp"
49 
50 namespace cmdline = utils::cmdline;
51 namespace text = utils::text;
52 
53 using utils::none;
54 using utils::optional;
55 
56 
57 namespace {
58 
59 
60 /// Reopens stdout as a tty and returns its width.
61 ///
62 /// \return The width of the tty in columns.  If the width is wider than 80, the
63 /// result is 5 columns narrower to match the screen_width() algorithm.
64 static std::size_t
65 reopen_stdout(void)
66 {
67     const int fd = ::open("/dev/tty", O_WRONLY);
68     if (fd == -1)
69         ATF_SKIP(F("Cannot open tty for test: %s") % ::strerror(errno));
70     struct ::winsize ws;
71     if (::ioctl(fd, TIOCGWINSZ, &ws) == -1)
72         ATF_SKIP(F("Cannot determine size of tty: %s") % ::strerror(errno));
73 
74     if (fd != STDOUT_FILENO) {
75         if (::dup2(fd, STDOUT_FILENO) == -1)
76             ATF_SKIP(F("Failed to redirect stdout: %s") % ::strerror(errno));
77         ::close(fd);
78     }
79 
80     return ws.ws_col >= 80 ? ws.ws_col - 5 : ws.ws_col;
81 }
82 
83 
84 }  // anonymous namespace
85 
86 
87 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__no_tty);
88 ATF_TEST_CASE_BODY(ui__screen_width__columns_set__no_tty)
89 {
90     utils::setenv("COLUMNS", "4321");
91     ::close(STDOUT_FILENO);
92 
93     cmdline::ui ui;
94     ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
95 }
96 
97 
98 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__tty);
99 ATF_TEST_CASE_BODY(ui__screen_width__columns_set__tty)
100 {
101     utils::setenv("COLUMNS", "4321");
102     (void)reopen_stdout();
103 
104     cmdline::ui ui;
105     ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
106 }
107 
108 
109 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__no_tty);
110 ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__no_tty)
111 {
112     utils::setenv("COLUMNS", "");
113     ::close(STDOUT_FILENO);
114 
115     cmdline::ui ui;
116     ATF_REQUIRE(!ui.screen_width());
117 }
118 
119 
120 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__tty);
121 ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__tty)
122 {
123     utils::setenv("COLUMNS", "");
124     const std::size_t columns = reopen_stdout();
125 
126     cmdline::ui ui;
127     ATF_REQUIRE_EQ(columns, ui.screen_width().get());
128 }
129 
130 
131 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__no_tty);
132 ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__no_tty)
133 {
134     utils::setenv("COLUMNS", "foo bar");
135     ::close(STDOUT_FILENO);
136 
137     cmdline::ui ui;
138     ATF_REQUIRE(!ui.screen_width());
139 }
140 
141 
142 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__tty);
143 ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__tty)
144 {
145     utils::setenv("COLUMNS", "foo bar");
146     const std::size_t columns = reopen_stdout();
147 
148     cmdline::ui ui;
149     ATF_REQUIRE_EQ(columns, ui.screen_width().get());
150 }
151 
152 
153 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__tty_is_file);
154 ATF_TEST_CASE_BODY(ui__screen_width__tty_is_file)
155 {
156     utils::unsetenv("COLUMNS");
157     const int fd = ::open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0755);
158     ATF_REQUIRE(fd != -1);
159     if (fd != STDOUT_FILENO) {
160         ATF_REQUIRE(::dup2(fd, STDOUT_FILENO) != -1);
161         ::close(fd);
162     }
163 
164     cmdline::ui ui;
165     ATF_REQUIRE(!ui.screen_width());
166 }
167 
168 
169 ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__cached);
170 ATF_TEST_CASE_BODY(ui__screen_width__cached)
171 {
172     cmdline::ui ui;
173 
174     utils::setenv("COLUMNS", "100");
175     ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
176 
177     utils::setenv("COLUMNS", "80");
178     ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
179 
180     utils::unsetenv("COLUMNS");
181     ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
182 }
183 
184 
185 ATF_TEST_CASE_WITHOUT_HEAD(ui__err);
186 ATF_TEST_CASE_BODY(ui__err)
187 {
188     cmdline::ui_mock ui(10);  // Keep shorter than message.
189     ui.err("This is a short message");
190     ATF_REQUIRE_EQ(1, ui.err_log().size());
191     ATF_REQUIRE_EQ("This is a short message", ui.err_log()[0]);
192     ATF_REQUIRE(ui.out_log().empty());
193 }
194 
195 
196 ATF_TEST_CASE_WITHOUT_HEAD(ui__out);
197 ATF_TEST_CASE_BODY(ui__out)
198 {
199     cmdline::ui_mock ui(10);  // Keep shorter than message.
200     ui.out("This is a short message");
201     ATF_REQUIRE(ui.err_log().empty());
202     ATF_REQUIRE_EQ(1, ui.out_log().size());
203     ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
204 }
205 
206 
207 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__no_refill);
208 ATF_TEST_CASE_BODY(ui__out_wrap__no_refill)
209 {
210     cmdline::ui_mock ui(100);
211     ui.out_wrap("This is a short message");
212     ATF_REQUIRE(ui.err_log().empty());
213     ATF_REQUIRE_EQ(1, ui.out_log().size());
214     ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
215 }
216 
217 
218 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__refill);
219 ATF_TEST_CASE_BODY(ui__out_wrap__refill)
220 {
221     cmdline::ui_mock ui(16);
222     ui.out_wrap("This is a short message");
223     ATF_REQUIRE(ui.err_log().empty());
224     ATF_REQUIRE_EQ(2, ui.out_log().size());
225     ATF_REQUIRE_EQ("This is a short", ui.out_log()[0]);
226     ATF_REQUIRE_EQ("message", ui.out_log()[1]);
227 }
228 
229 
230 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__no_refill);
231 ATF_TEST_CASE_BODY(ui__out_tag_wrap__no_refill)
232 {
233     cmdline::ui_mock ui(100);
234     ui.out_tag_wrap("Some long tag: ", "This is a short message");
235     ATF_REQUIRE(ui.err_log().empty());
236     ATF_REQUIRE_EQ(1, ui.out_log().size());
237     ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
238 }
239 
240 
241 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__repeat);
242 ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__repeat)
243 {
244     cmdline::ui_mock ui(32);
245     ui.out_tag_wrap("Some long tag: ", "This is a short message");
246     ATF_REQUIRE(ui.err_log().empty());
247     ATF_REQUIRE_EQ(2, ui.out_log().size());
248     ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
249     ATF_REQUIRE_EQ("Some long tag: message", ui.out_log()[1]);
250 }
251 
252 
253 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__no_repeat);
254 ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__no_repeat)
255 {
256     cmdline::ui_mock ui(32);
257     ui.out_tag_wrap("Some long tag: ", "This is a short message", false);
258     ATF_REQUIRE(ui.err_log().empty());
259     ATF_REQUIRE_EQ(2, ui.out_log().size());
260     ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
261     ATF_REQUIRE_EQ("               message", ui.out_log()[1]);
262 }
263 
264 
265 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__tag_too_long);
266 ATF_TEST_CASE_BODY(ui__out_tag_wrap__tag_too_long)
267 {
268     cmdline::ui_mock ui(5);
269     ui.out_tag_wrap("Some long tag: ", "This is a short message");
270     ATF_REQUIRE(ui.err_log().empty());
271     ATF_REQUIRE_EQ(1, ui.out_log().size());
272     ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
273 }
274 
275 
276 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__empty);
277 ATF_TEST_CASE_BODY(ui__out_table__empty)
278 {
279     const text::table table(3);
280 
281     text::table_formatter formatter;
282     formatter.set_separator(" | ");
283     formatter.set_column_width(0, 23);
284     formatter.set_column_width(1, text::table_formatter::width_refill);
285 
286     cmdline::ui_mock ui(52);
287     ui.out_table(table, formatter, "    ");
288     ATF_REQUIRE(ui.out_log().empty());
289 }
290 
291 
292 ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__not_empty);
293 ATF_TEST_CASE_BODY(ui__out_table__not_empty)
294 {
295     text::table table(3);
296     {
297         text::table_row row;
298         row.push_back("First");
299         row.push_back("Second");
300         row.push_back("Third");
301         table.add_row(row);
302     }
303     {
304         text::table_row row;
305         row.push_back("Fourth with some text");
306         row.push_back("Fifth with some more text");
307         row.push_back("Sixth foo");
308         table.add_row(row);
309     }
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_EQ(4, ui.out_log().size());
319     ATF_REQUIRE_EQ("    First                   | Second     | Third",
320                    ui.out_log()[0]);
321     ATF_REQUIRE_EQ("    Fourth with some text   | Fifth with | Sixth foo",
322                    ui.out_log()[1]);
323     ATF_REQUIRE_EQ("                            | some more  | ",
324                    ui.out_log()[2]);
325     ATF_REQUIRE_EQ("                            | text       | ",
326                    ui.out_log()[3]);
327 }
328 
329 
330 ATF_TEST_CASE_WITHOUT_HEAD(print_error);
331 ATF_TEST_CASE_BODY(print_error)
332 {
333     cmdline::init("error-program");
334     cmdline::ui_mock ui;
335     cmdline::print_error(&ui, "The error");
336     ATF_REQUIRE(ui.out_log().empty());
337     ATF_REQUIRE_EQ(1, ui.err_log().size());
338     ATF_REQUIRE_EQ("error-program: E: The error.", ui.err_log()[0]);
339 }
340 
341 
342 ATF_TEST_CASE_WITHOUT_HEAD(print_info);
343 ATF_TEST_CASE_BODY(print_info)
344 {
345     cmdline::init("info-program");
346     cmdline::ui_mock ui;
347     cmdline::print_info(&ui, "The info");
348     ATF_REQUIRE(ui.out_log().empty());
349     ATF_REQUIRE_EQ(1, ui.err_log().size());
350     ATF_REQUIRE_EQ("info-program: I: The info.", ui.err_log()[0]);
351 }
352 
353 
354 ATF_TEST_CASE_WITHOUT_HEAD(print_warning);
355 ATF_TEST_CASE_BODY(print_warning)
356 {
357     cmdline::init("warning-program");
358     cmdline::ui_mock ui;
359     cmdline::print_warning(&ui, "The warning");
360     ATF_REQUIRE(ui.out_log().empty());
361     ATF_REQUIRE_EQ(1, ui.err_log().size());
362     ATF_REQUIRE_EQ("warning-program: W: The warning.", ui.err_log()[0]);
363 }
364 
365 
366 ATF_INIT_TEST_CASES(tcs)
367 {
368     ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__no_tty);
369     ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__tty);
370     ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__no_tty);
371     ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__tty);
372     ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__no_tty);
373     ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__tty);
374     ATF_ADD_TEST_CASE(tcs, ui__screen_width__tty_is_file);
375     ATF_ADD_TEST_CASE(tcs, ui__screen_width__cached);
376 
377     ATF_ADD_TEST_CASE(tcs, ui__err);
378     ATF_ADD_TEST_CASE(tcs, ui__out);
379 
380     ATF_ADD_TEST_CASE(tcs, ui__out_wrap__no_refill);
381     ATF_ADD_TEST_CASE(tcs, ui__out_wrap__refill);
382     ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__no_refill);
383     ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__repeat);
384     ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__no_repeat);
385     ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__tag_too_long);
386     ATF_ADD_TEST_CASE(tcs, ui__out_table__empty);
387     ATF_ADD_TEST_CASE(tcs, ui__out_table__not_empty);
388 
389     ATF_ADD_TEST_CASE(tcs, print_error);
390     ATF_ADD_TEST_CASE(tcs, print_info);
391     ATF_ADD_TEST_CASE(tcs, print_warning);
392 }
393