1#!/usr/bin/perl 2# 3# Basic test suite for the Term::ANSIColor Perl module. 4# 5# Copyright 1997-1998, 2000-2002, 2005-2006, 2009-2010, 2012, 2014, 2020 6# Russ Allbery <rra@cpan.org> 7# 8# SPDX-License-Identifier: GPL-1.0-or-later OR Artistic-1.0-Perl 9 10use 5.008; 11use strict; 12use warnings; 13 14use Test::More tests => 169; 15 16# Load the module. 17BEGIN { 18 delete $ENV{ANSI_COLORS_ALIASES}; 19 delete $ENV{ANSI_COLORS_DISABLED}; 20 delete $ENV{NO_COLOR}; 21 use_ok('Term::ANSIColor', 22 qw(:pushpop color colored uncolor colorstrip colorvalid)); 23} 24 25# Various basic tests. 26is(color('blue on_green', 'bold'), "\e[34;42;1m", 'Simple attributes'); 27is(colored('testing', 'blue', 'bold'), "\e[34;1mtesting\e[0m", 'colored'); 28is((BLUE BOLD 'testing'), "\e[34m\e[1mtesting", 'Constants'); 29is(join(q{}, BLUE, BOLD, 'testing'), 30 "\e[34m\e[1mtesting", 'Constants with commas'); 31is((BLUE 'test', 'ing'), "\e[34mtesting", 'Constants with multiple strings'); 32 33# Test case variations on attributes. 34is(color('Blue BOLD', 'on_GReeN'), "\e[34;1;42m", 'Attribute case'); 35 36# color should return undef if there were no attributes. 37is(color(), undef, 'color returns undef with no attributes'); 38 39# Autoreset after the end of a command string. 40$Term::ANSIColor::AUTORESET = 1; 41is((BLUE BOLD 'testing'), "\e[34m\e[1mtesting\e[0m\e[0m", 'AUTORESET'); 42is((BLUE BOLD, 'te', 'st'), "\e[34m\e[1mtest\e[0m", 'AUTORESET with commas'); 43$Term::ANSIColor::AUTORESET = 0; 44 45# Reset after each line terminator. 46$Term::ANSIColor::EACHLINE = "\n"; 47is(colored("test\n\ntest", 'bold'), 48 "\e[1mtest\e[0m\n\n\e[1mtest\e[0m", 'EACHLINE'); 49$Term::ANSIColor::EACHLINE = "\r\n"; 50is( 51 colored("test\ntest\r\r\n\r\n", 'bold'), 52 "\e[1mtest\ntest\r\e[0m\r\n\r\n", 53 'EACHLINE with multiple delimiters' 54); 55$Term::ANSIColor::EACHLINE = "\n"; 56is( 57 colored(['bold', 'on_green'], "test\n", "\n", 'test'), 58 "\e[1;42mtest\e[0m\n\n\e[1;42mtest\e[0m", 59 'colored with reference to array' 60); 61 62# Basic tests for uncolor. 63is_deeply([uncolor('1;42', "\e[m", q{}, "\e[0m")], 64 [qw(bold on_green clear)], 'uncolor'); 65is_deeply([uncolor("\e[01m")], ['bold'], 'uncolor("\\e[01m")'); 66is_deeply([uncolor("\e[m")], [], 'uncolor("\\e[m")'); 67is_deeply([uncolor(q{})], [], 'uncolor("")'); 68 69# Several tests for ANSI_COLORS_DISABLED. 70local $ENV{ANSI_COLORS_DISABLED} = 1; 71is(color('blue'), q{}, 'color support for ANSI_COLORS_DISABLED'); 72is(colored('testing', 'blue', 'on_red'), 73 'testing', 'colored support for ANSI_COLORS_DISABLED'); 74is((GREEN 'testing'), 'testing', 'Constant support for ANSI_COLORS_DISABLED'); 75delete $ENV{ANSI_COLORS_DISABLED}; 76 77# Earlier versions of Term::ANSIColor didn't support ANSI_COLORS_DISABLED if 78# the constant had been created before the environment variable was set. Test 79# all the ones we're going to use to get full test coverage. 80local $ENV{ANSI_COLORS_DISABLED} = 1; 81is((BLUE 'testing'), 'testing', 'ANSI_COLORS_DISABLED with existing constant'); 82delete $ENV{ANSI_COLORS_DISABLED}; 83 84# If ANSI_COLORS_DISABLED is set to a false value or the empty string, it 85# should not take effect. 86local $ENV{ANSI_COLORS_DISABLED} = 0; 87is(color('bold'), "\e[1m", 'ANSI_COLORS_DISABLED must be true'); 88is((BOLD), "\e[1m", '...likewise for constants'); 89local $ENV{ANSI_COLORS_DISABLED} = q{}; 90is(color('bold'), "\e[1m", '...likewise when set to an empty string'); 91is((BOLD), "\e[1m", '...likewise for constants'); 92delete $ENV{ANSI_COLORS_DISABLED}; 93 94# Similar tests for NO_COLOR, although NO_COLOR may be set to any value. 95local $ENV{NO_COLOR} = 1; 96is(color('blue'), q{}, 'color support for NO_COLOR'); 97is(colored('testing', 'blue', 'on_red'), 98 'testing', 'colored support for NO_COLOR'); 99is((BLUE 'testing'), 'testing', 'Constant support for NO_COLOR'); 100local $ENV{NO_COLOR} = q{}; 101is(color('blue'), q{}, 'color support for NO_COLOR with empty string'); 102is((RED 'testing'), 103 'testing', 'Constant support for NO_COLOR with empty string'); 104delete $ENV{NO_COLOR}; 105 106# Make sure DARK is exported. This was omitted in versions prior to 1.07. 107is((DARK 'testing'), "\e[2mtesting", 'DARK'); 108 109# Check faint as a synonym for dark. 110is(colored('test', 'faint'), "\e[2mtest\e[0m", 'colored supports faint'); 111is((FAINT 'test'), "\e[2mtest", '...and the FAINT constant works'); 112 113# Test bright color support. 114is(color('bright_red'), "\e[91m", 'Bright red is supported'); 115is((BRIGHT_RED 'test'), "\e[91mtest", '...and as a constant'); 116is(color('on_bright_red'), "\e[101m", '...as is on bright red'); 117is((ON_BRIGHT_RED 'test'), "\e[101mtest", '...and as a constant'); 118 119# Test italic, which was added in 3.02. 120is(color('italic'), "\e[3m", 'Italic is supported'); 121is((ITALIC 'test'), "\e[3mtest", '...and as a constant'); 122 123# Test colored with 0 and EACHLINE. Regression test for an incorrect use of a 124# truth check. 125$Term::ANSIColor::EACHLINE = "\n"; 126is(colored('0', 'blue', 'bold'), 127 "\e[34;1m0\e[0m", 'colored with 0 and EACHLINE'); 128is( 129 colored("0\n0\n\n", 'blue', 'bold'), 130 "\e[34;1m0\e[0m\n\e[34;1m0\e[0m\n\n", 131 'colored with 0, EACHLINE, and multiple lines' 132); 133 134# Test colored with the empty string and EACHLINE. 135is(colored(q{}, 'blue', 'bold'), q{}, 'colored w/empty string and EACHLINE'); 136 137# Test push and pop support. 138is((PUSHCOLOR RED ON_GREEN 'text'), 139 "\e[31m\e[42mtext", 'PUSHCOLOR does not break constants'); 140is((PUSHCOLOR BLUE 'text'), "\e[34mtext", '...and adding another level'); 141is((RESET BLUE 'text'), "\e[0m\e[34mtext", '...and using reset'); 142is((POPCOLOR 'text'), "\e[31m\e[42mtext", '...and POPCOLOR works'); 143is((LOCALCOLOR GREEN ON_BLUE 'text'), 144 "\e[32m\e[44mtext\e[31m\e[42m", 'LOCALCOLOR'); 145$Term::ANSIColor::AUTOLOCAL = 1; 146is((BLUE 'text'), "\e[34mtext\e[31m\e[42m", 'AUTOLOCAL'); 147is((BLUE 'te', 'xt'), "\e[34mtext\e[31m\e[42m", 'AUTOLOCAL with commas'); 148$Term::ANSIColor::AUTOLOCAL = 0; 149is((POPCOLOR 'text'), "\e[0mtext", 'POPCOLOR with empty stack'); 150 151# If AUTOLOCAL and AUTORESET are both set, the former takes precedence. 152is((PUSHCOLOR RED ON_GREEN 'text'), 153 "\e[31m\e[42mtext", 'Push some colors onto the stack'); 154$Term::ANSIColor::AUTOLOCAL = 1; 155$Term::ANSIColor::AUTORESET = 1; 156is((BLUE 'text'), "\e[34mtext\e[31m\e[42m", 'AUTOLOCAL overrides AUTORESET'); 157$Term::ANSIColor::AUTOLOCAL = 0; 158is((BLUE 'text'), "\e[34mtext\e[0m", 'AUTORESET works with stacked colors'); 159is((POPCOLOR 'text'), "\e[0mtext\e[0m", 'POPCOLOR with empty stack'); 160$Term::ANSIColor::AUTORESET = 0; 161 162# Test push and pop support with the syntax from the original openmethods.com 163# submission, which uses a different coding style. 164is(PUSHCOLOR(RED ON_GREEN), "\e[31m\e[42m", 'PUSHCOLOR with explict argument'); 165is(PUSHCOLOR(BLUE), "\e[34m", '...and another explicit argument'); 166is( 167 RESET . BLUE . 'text', 168 "\e[0m\e[34mtext", 169 '...and constants with concatenation' 170); 171is( 172 POPCOLOR . 'text', 173 "\e[31m\e[42mtext", 174 '...and POPCOLOR works without an argument' 175); 176is( 177 LOCALCOLOR(GREEN . ON_BLUE . 'text'), 178 "\e[32m\e[44mtext\e[31m\e[42m", 179 'LOCALCOLOR with two arguments' 180); 181is(POPCOLOR . 'text', "\e[0mtext", 'POPCOLOR with no arguments'); 182 183# Prior to Term::ANSIColor, PUSHCOLOR, unlike all other constants, didn't take 184# an array, so it could lose colors in some syntax. 185is(PUSHCOLOR(RED, ON_GREEN), "\e[31m\e[42m", 'PUSHCOLOR with two arguments'); 186is( 187 LOCALCOLOR(GREEN, 'text'), 188 "\e[32mtext\e[31m\e[42m", 189 'LOCALCOLOR with two arguments' 190); 191is(POPCOLOR(BOLD, 'text'), "\e[0m\e[1mtext", 'POPCOLOR with two arguments'); 192 193# Test colorstrip. 194is( 195 colorstrip("\e[1mBold \e[31;42mon green\e[0m\e[m"), 196 'Bold on green', 197 'Basic color stripping' 198); 199is(colorstrip("\e[1m", 'bold', "\e[0m"), 200 'bold', 'Color stripping across multiple strings'); 201is_deeply( 202 [colorstrip("\e[1m", 'bold', "\e[0m")], 203 [q{}, 'bold', q{}], 204 '...and in an array context' 205); 206is(colorstrip("foo\e[1m", 'bar', "baz\e[0m"), 207 'foobarbaz', '...and proper joining in scalar context'); 208is( 209 colorstrip("\e[2cSome other code\e and stray [0m stuff"), 210 "\e[2cSome other code\e and stray [0m stuff", 211 'colorstrip does not remove non-color stuff' 212); 213 214# Test colorvalid. 215ok( 216 colorvalid('blue bold dark', 'blink on_green'), 217 'colorvalid returns true for valid attributes' 218); 219ok(!colorvalid('green orange'), '...and false for invalid attributes'); 220 221# Test error handling in color. 222my $output = eval { color('chartreuse') }; 223is($output, undef, 'color on unknown color name fails'); 224like( 225 $@, 226 qr{ \A Invalid [ ] attribute [ ] name [ ] chartreuse [ ] at [ ] }xms, 227 '...with the right error' 228); 229 230# Test error handling in colored. 231$output = eval { colored('Stuff', 'chartreuse') }; 232is($output, undef, 'colored on unknown color name fails'); 233like( 234 $@, 235 qr{ \A Invalid [ ] attribute [ ] name [ ] chartreuse [ ] at [ ] }xms, 236 '...with the right error' 237); 238 239# Test error handling in uncolor. 240$output = eval { uncolor "\e[28m" }; 241is($output, undef, 'uncolor on unknown color code fails'); 242like( 243 $@, 244 qr{ \A No [ ] name [ ] for [ ] escape [ ] sequence [ ] 28 [ ] at [ ] }xms, 245 '...with the right error' 246); 247$output = eval { uncolor "\e[foom" }; 248is($output, undef, 'uncolor on bad escape sequence fails'); 249like( 250 $@, 251 qr{ \A Bad [ ] escape [ ] sequence [ ] foo [ ] at [ ] }xms, 252 '...with the right error' 253); 254 255# Test error reporting when calling unrecognized Term::ANSIColor subs that go 256# through AUTOLOAD. 257ok(!eval { Term::ANSIColor::RSET() }, 'Running invalid constant'); 258like( 259 $@, 260 qr{ \A undefined [ ] subroutine [ ] \&Term::ANSIColor::RSET [ ] called 261 [ ] at [ ] }xms, 262 'Correct error from an attribute that is not defined' 263); 264ok(!eval { Term::ANSIColor::reset() }, 'Running invalid sub'); 265like( 266 $@, 267 qr{ \A undefined [ ] subroutine [ ] \&Term::ANSIColor::reset [ ] called 268 [ ] at [ ] }xms, 269 'Correct error from a lowercase attribute' 270); 271 272# Ensure that we still get proper error reporting for unknown constants when 273# when colors are disabled. 274local $ENV{ANSI_COLORS_DISABLED} = 1; 275eval { Term::ANSIColor::RSET() }; 276like( 277 $@, 278 qr{ \A undefined [ ] subroutine [ ] \&Term::ANSIColor::RSET [ ] called 279 [ ] at [ ] }xms, 280 'Correct error from undefined attribute with disabled colors' 281); 282delete $ENV{ANSI_COLORS_DISABLED}; 283 284# These are somewhat redundant, but they ensure we test all the branches in 285# our generated constant subs so that we can use Test::Strict to check test 286# suite coverage. 287is((BOLD 't'), "\e[1mt", 'Basic constant works for BOLD'); 288is((BLUE 't'), "\e[34mt", '...and for BLUE'); 289is((GREEN 't'), "\e[32mt", '...and for GREEN'); 290is((DARK 't'), "\e[2mt", '...and for DARK'); 291is((FAINT 't'), "\e[2mt", '...and for FAINT'); 292is((BRIGHT_RED 't'), "\e[91mt", '...and for BRIGHT_RED'); 293is((ON_BRIGHT_RED 't'), "\e[101mt", '...and for ON_BRIGHT_RED'); 294is((ITALIC 't'), "\e[3mt", '...and for ITALIC'); 295is((RED 't'), "\e[31mt", '...and for RED'); 296is((ON_GREEN 't'), "\e[42mt", '...and for ON_GREEN'); 297is((ON_BLUE 't'), "\e[44mt", '...and for ON_BLUE'); 298is((RESET 't'), "\e[0mt", '...and for RESET'); 299 300# Do the same for disabled colors. 301local $ENV{ANSI_COLORS_DISABLED} = 1; 302is(BOLD, q{}, 'ANSI_COLORS_DISABLED works for BOLD'); 303is(BLUE, q{}, '...and for BLUE'); 304is(GREEN, q{}, '...and for GREEN'); 305is(DARK, q{}, '...and for DARK'); 306is(FAINT, q{}, '...and for FAINT'); 307is(BRIGHT_RED, q{}, '...and for BRIGHT_RED'); 308is(ON_BRIGHT_RED, q{}, '...and for ON_BRIGHT_RED'); 309is(ITALIC, q{}, '...and for ITALIC'); 310is(RED, q{}, '...and for RED'); 311is(ON_GREEN, q{}, '...and for ON_GREEN'); 312is(ON_BLUE, q{}, '...and for ON_BLUE'); 313is(RESET, q{}, '...and for RESET'); 314delete $ENV{ANSI_COLORS_DISABLED}; 315 316# Do the same for disabled colors with NO_COLOR. 317local $ENV{NO_COLOR} = 1; 318is(BOLD, q{}, 'NO_COLOR works for BOLD'); 319is(BLUE, q{}, '...and for BLUE'); 320is(GREEN, q{}, '...and for GREEN'); 321is(DARK, q{}, '...and for DARK'); 322is(FAINT, q{}, '...and for FAINT'); 323is(BRIGHT_RED, q{}, '...and for BRIGHT_RED'); 324is(ON_BRIGHT_RED, q{}, '...and for ON_BRIGHT_RED'); 325is(ITALIC, q{}, '...and for ITALIC'); 326is(RED, q{}, '...and for RED'); 327is(ON_GREEN, q{}, '...and for ON_GREEN'); 328is(ON_BLUE, q{}, '...and for ON_BLUE'); 329is(RESET, q{}, '...and for RESET'); 330delete $ENV{NO_COLOR}; 331 332# Do the same for AUTORESET. 333$Term::ANSIColor::AUTORESET = 1; 334is((BOLD 't'), "\e[1mt\e[0m", 'AUTORESET works for BOLD'); 335is((BLUE 't'), "\e[34mt\e[0m", '...and for BLUE'); 336is((GREEN 't'), "\e[32mt\e[0m", '...and for GREEN'); 337is((DARK 't'), "\e[2mt\e[0m", '...and for DARK'); 338is((FAINT 't'), "\e[2mt\e[0m", '...and for FAINT'); 339is((BRIGHT_RED 't'), "\e[91mt\e[0m", '...and for BRIGHT_RED'); 340is((ON_BRIGHT_RED 't'), "\e[101mt\e[0m", '...and for ON_BRIGHT_RED'); 341is((ITALIC 't'), "\e[3mt\e[0m", '...and for ITALIC'); 342is((RED 't'), "\e[31mt\e[0m", '...and for RED'); 343is((ON_GREEN 't'), "\e[42mt\e[0m", '...and for ON_GREEN'); 344is((ON_BLUE 't'), "\e[44mt\e[0m", '...and for ON_BLUE'); 345is((RESET 't'), "\e[0mt\e[0m", '...and for RESET'); 346is((BOLD), "\e[1m", 'AUTORESET without text for BOLD'); 347is((BLUE), "\e[34m", '...and for BLUE'); 348is((GREEN), "\e[32m", '...and for GREEN'); 349is((DARK), "\e[2m", '...and for DARK'); 350is((FAINT), "\e[2m", '...and for FAINT'); 351is((BRIGHT_RED), "\e[91m", '...and for BRIGHT_RED'); 352is((ON_BRIGHT_RED), "\e[101m", '...and for ON_BRIGHT_RED'); 353is((ITALIC), "\e[3m", '...and for ITALIC'); 354is((RED), "\e[31m", '...and for RED'); 355is((ON_GREEN), "\e[42m", '...and for ON_GREEN'); 356is((ON_BLUE), "\e[44m", '...and for ON_BLUE'); 357is((RESET), "\e[0m", '...and for RESET'); 358$Term::ANSIColor::AUTORESET = 0; 359 360# Do the same for AUTOLOCAL. 361$Term::ANSIColor::AUTOLOCAL = 1; 362is((BOLD 't'), "\e[1mt\e[0m", 'AUTOLOCAL works for BOLD'); 363is((BLUE 't'), "\e[34mt\e[0m", '...and for BLUE'); 364is((GREEN 't'), "\e[32mt\e[0m", '...and for GREEN'); 365is((DARK 't'), "\e[2mt\e[0m", '...and for DARK'); 366is((FAINT 't'), "\e[2mt\e[0m", '...and for FAINT'); 367is((BRIGHT_RED 't'), "\e[91mt\e[0m", '...and for BRIGHT_RED'); 368is((ON_BRIGHT_RED 't'), "\e[101mt\e[0m", '...and for ON_BRIGHT_RED'); 369is((ITALIC 't'), "\e[3mt\e[0m", '...and for ITALIC'); 370is((RED 't'), "\e[31mt\e[0m", '...and for RED'); 371is((ON_GREEN 't'), "\e[42mt\e[0m", '...and for ON_GREEN'); 372is((ON_BLUE 't'), "\e[44mt\e[0m", '...and for ON_BLUE'); 373is((RESET 't'), "\e[0mt\e[0m", '...and for RESET'); 374is((BOLD), "\e[1m", 'AUTOLOCAL without text for BOLD'); 375is((BLUE), "\e[34m", '...and for BLUE'); 376is((GREEN), "\e[32m", '...and for GREEN'); 377is((DARK), "\e[2m", '...and for DARK'); 378is((FAINT), "\e[2m", '...and for FAINT'); 379is((BRIGHT_RED), "\e[91m", '...and for BRIGHT_RED'); 380is((ON_BRIGHT_RED), "\e[101m", '...and for ON_BRIGHT_RED'); 381is((ITALIC), "\e[3m", '...and for ITALIC'); 382is((RED), "\e[31m", '...and for RED'); 383is((ON_GREEN), "\e[42m", '...and for ON_GREEN'); 384is((ON_BLUE), "\e[44m", '...and for ON_BLUE'); 385is((RESET), "\e[0m", '...and for RESET'); 386$Term::ANSIColor::AUTOLOCAL = 0; 387 388# Force an internal error inside the AUTOLOAD stub by creating an attribute 389# that will generate a syntax error. This is just for coverage purposes. 390# Disable warnings since our syntax error will spew otherwise. 391local $SIG{__WARN__} = sub { }; 392$Term::ANSIColor::ATTRIBUTES{yellow} = q{'ERROR'}; 393ok(!eval { YELLOW 't' }, 'Caught internal AUTOLOAD error'); 394like( 395 $@, 396 qr{ \A failed [ ] to [ ] generate [ ] constant [ ] YELLOW: [ ] }xms, 397 '...with correct error message' 398); 399