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
reopen_stdout(void)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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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
ATF_INIT_TEST_CASES(tcs)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