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
31extern "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
50namespace cmdline = utils::cmdline;
51namespace text = utils::text;
52
53using utils::none;
54using utils::optional;
55
56
57namespace {
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.
64static std::size_t
65reopen_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
87ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__no_tty);
88ATF_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
98ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__tty);
99ATF_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
109ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__no_tty);
110ATF_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
120ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__tty);
121ATF_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
131ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__no_tty);
132ATF_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
142ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__tty);
143ATF_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
153ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__tty_is_file);
154ATF_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
169ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__cached);
170ATF_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
185ATF_TEST_CASE_WITHOUT_HEAD(ui__err);
186ATF_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
196ATF_TEST_CASE_WITHOUT_HEAD(ui__out);
197ATF_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
207ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__no_refill);
208ATF_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
218ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__refill);
219ATF_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
230ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__no_refill);
231ATF_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
241ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__repeat);
242ATF_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
253ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__no_repeat);
254ATF_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
265ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__tag_too_long);
266ATF_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
276ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__empty);
277ATF_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
292ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__not_empty);
293ATF_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
330ATF_TEST_CASE_WITHOUT_HEAD(print_error);
331ATF_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
342ATF_TEST_CASE_WITHOUT_HEAD(print_info);
343ATF_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
354ATF_TEST_CASE_WITHOUT_HEAD(print_warning);
355ATF_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
366ATF_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