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