# -*- coding: utf-8 -*-
import re
import os

import six

import pytest


def test_nothing_logged(testdir):
    testdir.makepyfile('''
        import sys

        def test_foo():
            sys.stdout.write('text going to stdout')
            sys.stderr.write('text going to stderr')
            assert False
        ''')
    result = testdir.runpytest()
    assert result.ret == 1
    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
                                 'text going to stdout'])
    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
                                 'text going to stderr'])
    with pytest.raises(pytest.fail.Exception):
        result.stdout.fnmatch_lines(['*- Captured *log call -*'])


def test_messages_logged(testdir):
    testdir.makepyfile('''
        import sys
        import logging

        logger = logging.getLogger(__name__)

        def test_foo():
            sys.stdout.write('text going to stdout')
            sys.stderr.write('text going to stderr')
            logger.info('text going to logger')
            assert False
        ''')
    result = testdir.runpytest('--log-level=INFO')
    assert result.ret == 1
    result.stdout.fnmatch_lines(['*- Captured *log call -*',
                                 '*text going to logger*'])
    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
                                 'text going to stdout'])
    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
                                 'text going to stderr'])


def test_root_logger_affected(testdir):
    testdir.makepyfile("""
        import logging
        logger = logging.getLogger()
        def test_foo():
            logger.info('info text ' + 'going to logger')
            logger.warning('warning text ' + 'going to logger')
            logger.error('error text ' + 'going to logger')

            assert 0
    """)
    log_file = testdir.tmpdir.join('pytest.log').strpath
    result = testdir.runpytest('--log-level=ERROR', '--log-file=pytest.log')
    assert result.ret == 1

    # the capture log calls in the stdout section only contain the
    # logger.error msg, because --log-level=ERROR
    result.stdout.fnmatch_lines(['*error text going to logger*'])
    with pytest.raises(pytest.fail.Exception):
        result.stdout.fnmatch_lines(['*warning text going to logger*'])
    with pytest.raises(pytest.fail.Exception):
        result.stdout.fnmatch_lines(['*info text going to logger*'])

    # the log file should contain the warning and the error log messages and
    # not the info one, because the default level of the root logger is
    # WARNING.
    assert os.path.isfile(log_file)
    with open(log_file) as rfh:
        contents = rfh.read()
        assert "info text going to logger" not in contents
        assert "warning text going to logger" in contents
        assert "error text going to logger" in contents


def test_log_cli_level_log_level_interaction(testdir):
    testdir.makepyfile("""
        import logging
        logger = logging.getLogger()

        def test_foo():
            logger.debug('debug text ' + 'going to logger')
            logger.info('info text ' + 'going to logger')
            logger.warning('warning text ' + 'going to logger')
            logger.error('error text ' + 'going to logger')
            assert 0
    """)

    result = testdir.runpytest('--log-cli-level=INFO', '--log-level=ERROR')
    assert result.ret == 1

    result.stdout.fnmatch_lines([
        '*-- live log call --*',
        '*INFO*info text going to logger',
        '*WARNING*warning text going to logger',
        '*ERROR*error text going to logger',
        '=* 1 failed in *=',
    ])
    assert 'DEBUG' not in result.stdout.str()


def test_setup_logging(testdir):
    testdir.makepyfile('''
        import logging

        logger = logging.getLogger(__name__)

        def setup_function(function):
            logger.info('text going to logger from setup')

        def test_foo():
            logger.info('text going to logger from call')
            assert False
    ''')
    result = testdir.runpytest('--log-level=INFO')
    assert result.ret == 1
    result.stdout.fnmatch_lines(['*- Captured *log setup -*',
                                 '*text going to logger from setup*',
                                 '*- Captured *log call -*',
                                 '*text going to logger from call*'])


def test_teardown_logging(testdir):
    testdir.makepyfile('''
        import logging

        logger = logging.getLogger(__name__)

        def test_foo():
            logger.info('text going to logger from call')

        def teardown_function(function):
            logger.info('text going to logger from teardown')
            assert False
        ''')
    result = testdir.runpytest('--log-level=INFO')
    assert result.ret == 1
    result.stdout.fnmatch_lines(['*- Captured *log call -*',
                                 '*text going to logger from call*',
                                 '*- Captured *log teardown -*',
                                 '*text going to logger from teardown*'])


def test_disable_log_capturing(testdir):
    testdir.makepyfile('''
        import sys
        import logging

        logger = logging.getLogger(__name__)

        def test_foo():
            sys.stdout.write('text going to stdout')
            logger.warning('catch me if you can!')
            sys.stderr.write('text going to stderr')
            assert False
        ''')
    result = testdir.runpytest('--no-print-logs')
    print(result.stdout)
    assert result.ret == 1
    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
                                 'text going to stdout'])
    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
                                 'text going to stderr'])
    with pytest.raises(pytest.fail.Exception):
        result.stdout.fnmatch_lines(['*- Captured *log call -*'])


def test_disable_log_capturing_ini(testdir):
    testdir.makeini(
        '''
        [pytest]
        log_print=False
        '''
    )
    testdir.makepyfile('''
        import sys
        import logging

        logger = logging.getLogger(__name__)

        def test_foo():
            sys.stdout.write('text going to stdout')
            logger.warning('catch me if you can!')
            sys.stderr.write('text going to stderr')
            assert False
        ''')
    result = testdir.runpytest()
    print(result.stdout)
    assert result.ret == 1
    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
                                 'text going to stdout'])
    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
                                 'text going to stderr'])
    with pytest.raises(pytest.fail.Exception):
        result.stdout.fnmatch_lines(['*- Captured *log call -*'])


@pytest.mark.parametrize('enabled', [True, False])
def test_log_cli_enabled_disabled(testdir, enabled):
    msg = 'critical message logged by test'
    testdir.makepyfile('''
        import logging
        def test_log_cli():
            logging.critical("{}")
    '''.format(msg))
    if enabled:
        testdir.makeini('''
            [pytest]
            log_cli=true
        ''')
    result = testdir.runpytest()
    if enabled:
        result.stdout.fnmatch_lines([
            'test_log_cli_enabled_disabled.py::test_log_cli ',
            '*-- live log call --*',
            'test_log_cli_enabled_disabled.py* CRITICAL critical message logged by test',
            'PASSED*',
        ])
    else:
        assert msg not in result.stdout.str()


def test_log_cli_default_level(testdir):
    # Default log file level
    testdir.makepyfile('''
        import pytest
        import logging
        def test_log_cli(request):
            plugin = request.config.pluginmanager.getplugin('logging-plugin')
            assert plugin.log_cli_handler.level == logging.NOTSET
            logging.getLogger('catchlog').info("INFO message won't be shown")
            logging.getLogger('catchlog').warning("WARNING message will be shown")
    ''')
    testdir.makeini('''
        [pytest]
        log_cli=true
    ''')

    result = testdir.runpytest()

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_cli_default_level.py::test_log_cli ',
        'test_log_cli_default_level.py*WARNING message will be shown*',
    ])
    assert "INFO message won't be shown" not in result.stdout.str()
    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0


def test_log_cli_default_level_multiple_tests(testdir, request):
    """Ensure we reset the first newline added by the live logger between tests"""
    filename = request.node.name + '.py'
    testdir.makepyfile('''
        import logging

        def test_log_1():
            logging.warning("log message from test_log_1")

        def test_log_2():
            logging.warning("log message from test_log_2")
    ''')
    testdir.makeini('''
        [pytest]
        log_cli=true
    ''')

    result = testdir.runpytest()
    result.stdout.fnmatch_lines([
        '{}::test_log_1 '.format(filename),
        '*WARNING*log message from test_log_1*',
        'PASSED *50%*',
        '{}::test_log_2 '.format(filename),
        '*WARNING*log message from test_log_2*',
        'PASSED *100%*',
        '=* 2 passed in *=',
    ])


def test_log_cli_default_level_sections(testdir, request):
    """Check that with live logging enable we are printing the correct headers during
    start/setup/call/teardown/finish."""
    filename = request.node.name + '.py'
    testdir.makeconftest('''
        import pytest
        import logging

        def pytest_runtest_logstart():
            logging.warning('>>>>> START >>>>>')

        def pytest_runtest_logfinish():
            logging.warning('<<<<< END <<<<<<<')
    ''')

    testdir.makepyfile('''
        import pytest
        import logging

        @pytest.fixture
        def fix(request):
            logging.warning("log message from setup of {}".format(request.node.name))
            yield
            logging.warning("log message from teardown of {}".format(request.node.name))

        def test_log_1(fix):
            logging.warning("log message from test_log_1")

        def test_log_2(fix):
            logging.warning("log message from test_log_2")
    ''')
    testdir.makeini('''
        [pytest]
        log_cli=true
    ''')

    result = testdir.runpytest()
    result.stdout.fnmatch_lines([
        '{}::test_log_1 '.format(filename),
        '*-- live log start --*',
        '*WARNING* >>>>> START >>>>>*',
        '*-- live log setup --*',
        '*WARNING*log message from setup of test_log_1*',
        '*-- live log call --*',
        '*WARNING*log message from test_log_1*',
        'PASSED *50%*',
        '*-- live log teardown --*',
        '*WARNING*log message from teardown of test_log_1*',
        '*-- live log finish --*',
        '*WARNING* <<<<< END <<<<<<<*',

        '{}::test_log_2 '.format(filename),
        '*-- live log start --*',
        '*WARNING* >>>>> START >>>>>*',
        '*-- live log setup --*',
        '*WARNING*log message from setup of test_log_2*',
        '*-- live log call --*',
        '*WARNING*log message from test_log_2*',
        'PASSED *100%*',
        '*-- live log teardown --*',
        '*WARNING*log message from teardown of test_log_2*',
        '*-- live log finish --*',
        '*WARNING* <<<<< END <<<<<<<*',
        '=* 2 passed in *=',
    ])


def test_live_logs_unknown_sections(testdir, request):
    """Check that with live logging enable we are printing the correct headers during
    start/setup/call/teardown/finish."""
    filename = request.node.name + '.py'
    testdir.makeconftest('''
        import pytest
        import logging

        def pytest_runtest_protocol(item, nextitem):
            logging.warning('Unknown Section!')

        def pytest_runtest_logstart():
            logging.warning('>>>>> START >>>>>')

        def pytest_runtest_logfinish():
            logging.warning('<<<<< END <<<<<<<')
    ''')

    testdir.makepyfile('''
        import pytest
        import logging

        @pytest.fixture
        def fix(request):
            logging.warning("log message from setup of {}".format(request.node.name))
            yield
            logging.warning("log message from teardown of {}".format(request.node.name))

        def test_log_1(fix):
            logging.warning("log message from test_log_1")

    ''')
    testdir.makeini('''
        [pytest]
        log_cli=true
    ''')

    result = testdir.runpytest()
    result.stdout.fnmatch_lines([
        '*WARNING*Unknown Section*',
        '{}::test_log_1 '.format(filename),
        '*WARNING* >>>>> START >>>>>*',
        '*-- live log setup --*',
        '*WARNING*log message from setup of test_log_1*',
        '*-- live log call --*',
        '*WARNING*log message from test_log_1*',
        'PASSED *100%*',
        '*-- live log teardown --*',
        '*WARNING*log message from teardown of test_log_1*',
        '*WARNING* <<<<< END <<<<<<<*',
        '=* 1 passed in *=',
    ])


def test_sections_single_new_line_after_test_outcome(testdir, request):
    """Check that only a single new line is written between log messages during
    teardown/finish."""
    filename = request.node.name + '.py'
    testdir.makeconftest('''
        import pytest
        import logging

        def pytest_runtest_logstart():
            logging.warning('>>>>> START >>>>>')

        def pytest_runtest_logfinish():
            logging.warning('<<<<< END <<<<<<<')
            logging.warning('<<<<< END <<<<<<<')
    ''')

    testdir.makepyfile('''
        import pytest
        import logging

        @pytest.fixture
        def fix(request):
            logging.warning("log message from setup of {}".format(request.node.name))
            yield
            logging.warning("log message from teardown of {}".format(request.node.name))
            logging.warning("log message from teardown of {}".format(request.node.name))

        def test_log_1(fix):
            logging.warning("log message from test_log_1")
    ''')
    testdir.makeini('''
        [pytest]
        log_cli=true
    ''')

    result = testdir.runpytest()
    result.stdout.fnmatch_lines([
        '{}::test_log_1 '.format(filename),
        '*-- live log start --*',
        '*WARNING* >>>>> START >>>>>*',
        '*-- live log setup --*',
        '*WARNING*log message from setup of test_log_1*',
        '*-- live log call --*',
        '*WARNING*log message from test_log_1*',
        'PASSED *100%*',
        '*-- live log teardown --*',
        '*WARNING*log message from teardown of test_log_1*',
        '*-- live log finish --*',
        '*WARNING* <<<<< END <<<<<<<*',
        '*WARNING* <<<<< END <<<<<<<*',
        '=* 1 passed in *=',
    ])
    assert re.search(r'(.+)live log teardown(.+)\n(.+)WARNING(.+)\n(.+)WARNING(.+)',
                     result.stdout.str(), re.MULTILINE) is not None
    assert re.search(r'(.+)live log finish(.+)\n(.+)WARNING(.+)\n(.+)WARNING(.+)',
                     result.stdout.str(), re.MULTILINE) is not None


def test_log_cli_level(testdir):
    # Default log file level
    testdir.makepyfile('''
        import pytest
        import logging
        def test_log_cli(request):
            plugin = request.config.pluginmanager.getplugin('logging-plugin')
            assert plugin.log_cli_handler.level == logging.INFO
            logging.getLogger('catchlog').debug("This log message won't be shown")
            logging.getLogger('catchlog').info("This log message will be shown")
            print('PASSED')
    ''')
    testdir.makeini('''
        [pytest]
        log_cli=true
    ''')

    result = testdir.runpytest('-s', '--log-cli-level=INFO')

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_cli_level.py*This log message will be shown',
        'PASSED',  # 'PASSED' on its own line because the log message prints a new line
    ])
    assert "This log message won't be shown" not in result.stdout.str()

    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0

    result = testdir.runpytest('-s', '--log-level=INFO')

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_cli_level.py* This log message will be shown',
        'PASSED',  # 'PASSED' on its own line because the log message prints a new line
    ])
    assert "This log message won't be shown" not in result.stdout.str()

    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0


def test_log_cli_ini_level(testdir):
    testdir.makeini(
        """
        [pytest]
        log_cli=true
        log_cli_level = INFO
        """)
    testdir.makepyfile('''
        import pytest
        import logging
        def test_log_cli(request):
            plugin = request.config.pluginmanager.getplugin('logging-plugin')
            assert plugin.log_cli_handler.level == logging.INFO
            logging.getLogger('catchlog').debug("This log message won't be shown")
            logging.getLogger('catchlog').info("This log message will be shown")
            print('PASSED')
    ''')

    result = testdir.runpytest('-s')

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_cli_ini_level.py* This log message will be shown',
        'PASSED',  # 'PASSED' on its own line because the log message prints a new line
    ])
    assert "This log message won't be shown" not in result.stdout.str()

    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0


@pytest.mark.parametrize('cli_args', ['',
                                      '--log-level=WARNING',
                                      '--log-file-level=WARNING',
                                      '--log-cli-level=WARNING'])
def test_log_cli_auto_enable(testdir, request, cli_args):
    """Check that live logs are enabled if --log-level or --log-cli-level is passed on the CLI.
    It should not be auto enabled if the same configs are set on the INI file.
    """
    testdir.makepyfile('''
        import pytest
        import logging

        def test_log_1():
            logging.info("log message from test_log_1 not to be shown")
            logging.warning("log message from test_log_1")

    ''')
    testdir.makeini('''
        [pytest]
        log_level=INFO
        log_cli_level=INFO
    ''')

    result = testdir.runpytest(cli_args)
    if cli_args == '--log-cli-level=WARNING':
        result.stdout.fnmatch_lines([
            '*::test_log_1 ',
            '*-- live log call --*',
            '*WARNING*log message from test_log_1*',
            'PASSED *100%*',
            '=* 1 passed in *=',
        ])
        assert 'INFO' not in result.stdout.str()
    else:
        result.stdout.fnmatch_lines([
            '*test_log_cli_auto_enable*100%*',
            '=* 1 passed in *=',
        ])
        assert 'INFO' not in result.stdout.str()
        assert 'WARNING' not in result.stdout.str()


def test_log_file_cli(testdir):
    # Default log file level
    testdir.makepyfile('''
        import pytest
        import logging
        def test_log_file(request):
            plugin = request.config.pluginmanager.getplugin('logging-plugin')
            assert plugin.log_file_handler.level == logging.WARNING
            logging.getLogger('catchlog').info("This log message won't be shown")
            logging.getLogger('catchlog').warning("This log message will be shown")
            print('PASSED')
    ''')

    log_file = testdir.tmpdir.join('pytest.log').strpath

    result = testdir.runpytest('-s', '--log-file={0}'.format(log_file), '--log-file-level=WARNING')

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_file_cli.py PASSED',
    ])

    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0
    assert os.path.isfile(log_file)
    with open(log_file) as rfh:
        contents = rfh.read()
        assert "This log message will be shown" in contents
        assert "This log message won't be shown" not in contents


def test_log_file_cli_level(testdir):
    # Default log file level
    testdir.makepyfile('''
        import pytest
        import logging
        def test_log_file(request):
            plugin = request.config.pluginmanager.getplugin('logging-plugin')
            assert plugin.log_file_handler.level == logging.INFO
            logging.getLogger('catchlog').debug("This log message won't be shown")
            logging.getLogger('catchlog').info("This log message will be shown")
            print('PASSED')
    ''')

    log_file = testdir.tmpdir.join('pytest.log').strpath

    result = testdir.runpytest('-s',
                               '--log-file={0}'.format(log_file),
                               '--log-file-level=INFO')

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_file_cli_level.py PASSED',
    ])

    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0
    assert os.path.isfile(log_file)
    with open(log_file) as rfh:
        contents = rfh.read()
        assert "This log message will be shown" in contents
        assert "This log message won't be shown" not in contents


def test_log_level_not_changed_by_default(testdir):
    testdir.makepyfile('''
        import logging
        def test_log_file():
            assert logging.getLogger().level == logging.WARNING
    ''')
    result = testdir.runpytest('-s')
    result.stdout.fnmatch_lines('* 1 passed in *')


def test_log_file_ini(testdir):
    log_file = testdir.tmpdir.join('pytest.log').strpath

    testdir.makeini(
        """
        [pytest]
        log_file={0}
        log_file_level=WARNING
        """.format(log_file))
    testdir.makepyfile('''
        import pytest
        import logging
        def test_log_file(request):
            plugin = request.config.pluginmanager.getplugin('logging-plugin')
            assert plugin.log_file_handler.level == logging.WARNING
            logging.getLogger('catchlog').info("This log message won't be shown")
            logging.getLogger('catchlog').warning("This log message will be shown")
            print('PASSED')
    ''')

    result = testdir.runpytest('-s')

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_file_ini.py PASSED',
    ])

    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0
    assert os.path.isfile(log_file)
    with open(log_file) as rfh:
        contents = rfh.read()
        assert "This log message will be shown" in contents
        assert "This log message won't be shown" not in contents


def test_log_file_ini_level(testdir):
    log_file = testdir.tmpdir.join('pytest.log').strpath

    testdir.makeini(
        """
        [pytest]
        log_file={0}
        log_file_level = INFO
        """.format(log_file))
    testdir.makepyfile('''
        import pytest
        import logging
        def test_log_file(request):
            plugin = request.config.pluginmanager.getplugin('logging-plugin')
            assert plugin.log_file_handler.level == logging.INFO
            logging.getLogger('catchlog').debug("This log message won't be shown")
            logging.getLogger('catchlog').info("This log message will be shown")
            print('PASSED')
    ''')

    result = testdir.runpytest('-s')

    # fnmatch_lines does an assertion internally
    result.stdout.fnmatch_lines([
        'test_log_file_ini_level.py PASSED',
    ])

    # make sure that that we get a '0' exit code for the testsuite
    assert result.ret == 0
    assert os.path.isfile(log_file)
    with open(log_file) as rfh:
        contents = rfh.read()
        assert "This log message will be shown" in contents
        assert "This log message won't be shown" not in contents


@pytest.mark.parametrize('has_capture_manager', [True, False])
def test_live_logging_suspends_capture(has_capture_manager, request):
    """Test that capture manager is suspended when we emitting messages for live logging.

    This tests the implementation calls instead of behavior because it is difficult/impossible to do it using
    ``testdir`` facilities because they do their own capturing.

    We parametrize the test to also make sure _LiveLoggingStreamHandler works correctly if no capture manager plugin
    is installed.
    """
    import logging
    from functools import partial
    from _pytest.capture import CaptureManager
    from _pytest.logging import _LiveLoggingStreamHandler

    class MockCaptureManager:
        calls = []

        def suspend_global_capture(self):
            self.calls.append('suspend_global_capture')

        def resume_global_capture(self):
            self.calls.append('resume_global_capture')

    # sanity check
    assert CaptureManager.suspend_capture_item
    assert CaptureManager.resume_global_capture

    class DummyTerminal(six.StringIO):

        def section(self, *args, **kwargs):
            pass

    out_file = DummyTerminal()
    capture_manager = MockCaptureManager() if has_capture_manager else None
    handler = _LiveLoggingStreamHandler(out_file, capture_manager)
    handler.set_when('call')

    logger = logging.getLogger(__name__ + '.test_live_logging_suspends_capture')
    logger.addHandler(handler)
    request.addfinalizer(partial(logger.removeHandler, handler))

    logger.critical('some message')
    if has_capture_manager:
        assert MockCaptureManager.calls == ['suspend_global_capture', 'resume_global_capture']
    else:
        assert MockCaptureManager.calls == []
    assert out_file.getvalue() == '\nsome message\n'