xref: /openbsd-src/gnu/usr.bin/perl/cpan/Term-ANSIColor/t/module/basic.t (revision de8cc8edbc71bd3e3bc7fbffa27ba0e564c37d8b)
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