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 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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 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