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