Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for testing colored strings #278

Open
Bhupesh-V opened this issue Aug 30, 2022 · 2 comments
Open

Support for testing colored strings #278

Bhupesh-V opened this issue Aug 30, 2022 · 2 comments

Comments

@Bhupesh-V
Copy link

A script with colored output like this

#!/usr/bin/env bash

BOLD=$(tput bold)
RESET=$(tput sgr0)

printf "%s\n" "${BOLD}Hello World${RESET}"

Fails the following test

Describe 'script.sh'

  setup() {
    BOLD=$(tput bold)
    RESET=$(tput sgr0)
  }
  It 'invokes correctly'
    When call ./script.sh
    The output should equal "${BOLD}Hello World${RESET}"
  End
End
@LukeSavefrogs
Copy link
Contributor

LukeSavefrogs commented Nov 10, 2022

Actually, it is already possible to compare colored text by using a custom matcher.

I created this matcher to remove all invisible control characters before searching the text:

# Custom matcher used to find a string inside of a text containing ANSI escape codes.
match_colored_text() {
	# Source: https://unix.stackexchange.com/a/18979/348102
	sanitized_text="$(echo "${match_colored_text:?}" | perl -e '
		while (<>) {
			s/ \e[ #%()*+\-.\/]. |
				\r | # Remove extra carriage returns also
				(?:\e\[|\x9b) [ -?]* [@-~] | # CSI ... Cmd
				(?:\e\]|\x9d) .*? (?:\e\\|[\a\x9c]) | # OSC ... (ST|BEL)
				(?:\e[P^_]|[\x90\x9e\x9f]) .*? (?:\e\\|\x9c) | # (DCS|PM|APC) ... ST
				\e.|[\x80-\x9f] //xg;
				1 while s/[^\b][\b]//g;  # remove all non-backspace followed by backspace
			print;
		}
	')"
	echo "${sanitized_text}" | grep -q "$1"
}

Then i used it like this:

Describe "should have help option..."
	Parameters
		"Short" "-h"
		"Long" "--help"
	End

	Example "$1 ($2)"
		Set 'errexit:on'

		When call "${LIBRARY_PATH}" $2
		The status should be success
		
		# Here i use a custom matcher function because the text has ANSI colors by default,
		# and this makes impossible to the `eq` matcher to find text.
		The output should satisfy match_colored_text "OPTIONS:"
		The output should satisfy match_colored_text "USAGE:"
	End
End

If you REALLY really need to compare even the colors (which i discourage and isn't best practice, since the user could disable them entirely on their terminal [and sometimes your code should too]) you could write a custom matcher (maybe in Perl?) that checks that even the colors are the same.

In the end...

Putting all together (if you choose to check that text is equal regardless of colors) in your case the code should look like this:

# Custom matcher used to find a string inside of a text containing ANSI escape codes.
match_colored_text() {
	# Source: https://unix.stackexchange.com/a/18979/348102
	sanitized_text="$(echo "${match_colored_text:?}" | perl -e '
		while (<>) {
			s/ \e[ #%()*+\-.\/]. |
				\r | # Remove extra carriage returns also
				(?:\e\[|\x9b) [ -?]* [@-~] | # CSI ... Cmd
				(?:\e\]|\x9d) .*? (?:\e\\|[\a\x9c]) | # OSC ... (ST|BEL)
				(?:\e[P^_]|[\x90\x9e\x9f]) .*? (?:\e\\|\x9c) | # (DCS|PM|APC) ... ST
				\e.|[\x80-\x9f] //xg;
				1 while s/[^\b][\b]//g;  # remove all non-backspace followed by backspace
			print;
		}
	')"
	echo "${sanitized_text}" | grep -q "$1"
}

Describe 'script.sh'
	It 'invokes correctly'
		When call ./script.sh
		The output should satisfy match_colored_text "Hello World"
	End
End

@LukeSavefrogs
Copy link
Contributor

LukeSavefrogs commented Nov 10, 2022

UPDATE

I managed to get it working both with and without interpreting escape codes:

In the following Shellspec test will be used two custom matchers:

  • match_plain_text: Converts the input text to plain text before checking;
  • match_rich_text: Checks if the strings are equal by interpreting escape codes too;

The following is a working (but slightly different) test i used in this project of mine:

%const LIBRARY_PATH: src/myscript.sh

# Custom matcher used to find a string inside of a plain text.
match_plain_text() {
	# Source: https://unix.stackexchange.com/a/18979/348102
	sanitized_text="$(echo "${match_plain_text:?}" | perl -e '
		while (<>) {
			s/ \e[ #%()*+\-.\/]. |
				\r | # Remove extra carriage returns also
				(?:\e\[|\x9b) [ -?]* [@-~] | # CSI ... Cmd
				(?:\e\]|\x9d) .*? (?:\e\\|[\a\x9c]) | # OSC ... (ST|BEL)
				(?:\e[P^_]|[\x90\x9e\x9f]) .*? (?:\e\\|\x9c) | # (DCS|PM|APC) ... ST
				\e.|[\x80-\x9f] //xg;
				1 while s/[^\b][\b]//g;  # remove all non-backspace followed by backspace
			print;
		}
	')"
	echo "${sanitized_text}" | grep -q "$1"
}

# Custom matcher used to find a string inside of a text containing ANSI escape codes.
match_rich_text() {
	if [ -z "${1}" ]; then
		printf "ERROR: You cannot pass an empty string!\n" >&2;
		return 1;
	fi
	
	# shellcheck disable=SC2059
	rich_search_term="$(printf "$1")"
	match="$(echo "${match_rich_text:-}" | perl -ne "print if /\Q${rich_search_term}/")"

	[ -n "${match}" ]
}

Describe "Testing text with or without colors:"
	Example "WITHOUT Colors:"
		Set 'errexit:on'


		When call "${LIBRARY_PATH}" --help
		The status should be success
		
		# Here i use a custom matcher function because the text has ANSI colors by default,
		# and this makes impossible to the `eq` matcher to find text.
		The output should satisfy match_plain_text "OPTIONS:"
		The output should satisfy match_plain_text "USAGE:"
	End

	Example "WITH Colors:"
		Set 'errexit:on'


		When call "${LIBRARY_PATH}" --help
		The status should be success
		
		# Works with inline characters
		The output should satisfy match_rich_text "\033[1mOPTIONS\033[0m:"

		# But works with already rendered characters too!
		bold="$(printf '\033[1m')"
		reset="$(printf '\033[0m')"
		The output should satisfy match_rich_text "${bold}USAGE${reset}:"
	End
End

You could even make the custom matchers available globally (to all tests) by putting those two functions inside the {SPEC_HELPER_DIR}/spec_helper.sh file (usually {root}/spec/spec_helper.sh).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants