timofurrer / colorful

Terminal string styling done right, in Python :snake: :tada:
MIT License
525 stars 23 forks source link

Colors escape sequences got stripped off during test with pytest #23

Closed edouard-lopez closed 5 years ago

edouard-lopez commented 5 years ago

related: https://stackoverflow.com/q/54884561/802365


I'm re-implementing pure prompt in python so it can support more shell.

However, when testing color I got "unexecpected" behavior, i.e. one that I don't understand.

tests output

    def test_prompt_symbol_is_colored_for_successful_command():
        assert str(prompt.prompt_symbol()) == str(colors.primary('❯'))
>       assert str(prompt.prompt_symbol()) == '\x1b[38;2;155;48;255m❯\x1b[39m'
E       AssertionError: assert '❯' == '\x1b[38;2;155;48;255m❯\x1b[39m'
E         - ❯
E         + ❯   ← this one is purple

_colorstest.py

def test_prompt_symbol_is_colored_for_successful_command():
    assert str(prompt.prompt_symbol()) == str(colors.primary('❯'))
    assert str(prompt.prompt_symbol()) == '\x1b[38;2;155;48;255m❯\x1b[39m'

colors.py

import colorful

primary = colorful.purple1
mute = colorful.gray

prompt.py

from pure import colors

def prompt_symbol(last_command_status=0):
    symbol = colors.primary('❯') if last_command_status == 0 else colors.danger('❯')
    return symbol

Question

The first assertion succeed, while the second failed despite the fact they should be equivalent. When I negate the first assertion the escape sequence are not present:

>       assert str(prompt.prompt_symbol()) != str(colors.primary('❯'))
E       AssertionError: assert '❯' != '❯'
E        +  where '❯' = str(<colorful.core.ColorfulString object at 0x7f545276f080>)
E        +    where <colorful.core.ColorfulString object at 0x7f545276f080> = <function prompt_symbol at 0x7f54527b79d8>()
E        +      where <function prompt_symbol at 0x7f54527b79d8> = prompt.prompt_symbol
E        +  and   '❯' = str(<colorful.core.ColorfulString object at 0x7f545276f0b8>)
E        +    where <colorful.core.ColorfulString object at 0x7f545276f0b8> = <colorful.core.Colorful.ColorfulStyle object at 0x7f54527c2278>('❯')
E        +      where <colorful.core.Colorful.ColorfulStyle object at 0x7f54527c2278> = colors.primary

Manually executing command in python REPL gives me:

>>> from pure import colors, prompt 
>>> str(colors.primary('❯'))
'\x1b[38;2;155;48;255m❯\x1b[39m'
>>> str(prompt.prompt_symbol())
'\x1b[38;2;155;48;255m❯\x1b[39m'
sblondon commented 5 years ago

It seems the problem is linked to pytest. The assertion is True in a python interpreter, False in pytest:

Python 3.7.2+ (default, Feb  2 2019, 14:31:48) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import prompt
>>> str(prompt.prompt_symbol()) == '\x1b[38;2;155;48;255m' + prompt.sup + '\x1b[39m'
True

I made some changes to the example code:

prompt.py:

import colors

sup = '❯'

def prompt_symbol():
    return colors.primary(sup)

colors_test.py:

import colors
import prompt

def test_prompt_symbol_is_colored_for_successful_command():
    assert str(prompt.prompt_symbol()) == str(colors.primary(prompt.sup))
    assert str(prompt.prompt_symbol()) == '\x1b[38;2;155;48;255m' + prompt.sup + '\x1b[39m'
edouard-lopez commented 5 years ago

@timofurrer @Krilivye suggested to try --capture=no (or -s) flag from pytest.

That does fix my problem when running pytest -v --capture=no tests/, but I don't get why as I don't print to stdout nor stderr in the tested method.

Moreover it doesn't work when I use it through a makefile make tests:

tests:
    clear
    pytest --verbose --capture=no tests/
edouard-lopez commented 5 years ago

To fix the makefile, I move colorful.use_true_colors() from prompt.py to colors.py

timofurrer commented 5 years ago

It's because colorful disables colors automatically when it's piped into another stream.

edouard-lopez commented 5 years ago

@timofurrer Is it possible to disable this behavior other than the solution I used ?