1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the MIT License.  See the LICENSE file in the root of this
3# repository for complete details.
4
5from __future__ import absolute_import, division, print_function
6
7import sys
8
9import pytest
10
11from six.moves import cStringIO as StringIO
12
13from structlog._loggers import (
14    WRITE_LOCKS,
15    PrintLogger,
16    PrintLoggerFactory,
17    ReturnLogger,
18    ReturnLoggerFactory,
19)
20from structlog.stdlib import _NAME_TO_LEVEL
21
22
23def test_return_logger():
24    obj = ["hello"]
25    assert obj is ReturnLogger().msg(obj)
26
27
28STDLIB_MSG_METHODS = [m for m in _NAME_TO_LEVEL if m != "notset"]
29
30
31class TestPrintLogger(object):
32    def test_prints_to_stdout_by_default(self, capsys):
33        """
34        Instantiating without arguments gives conveniently a logger to standard
35        out.
36        """
37        PrintLogger().msg("hello")
38
39        out, err = capsys.readouterr()
40        assert "hello\n" == out
41        assert "" == err
42
43    def test_prints_to_correct_file(self, tmpdir, capsys):
44        """
45        Supplied files are respected.
46        """
47        f = tmpdir.join("test.log")
48        fo = f.open("w")
49        PrintLogger(fo).msg("hello")
50        out, err = capsys.readouterr()
51
52        assert "" == out == err
53        fo.close()
54        assert "hello\n" == f.read()
55
56    def test_repr(self):
57        """
58        __repr__ makes sense.
59        """
60        assert repr(PrintLogger()).startswith("<PrintLogger(file=")
61
62    def test_lock(self):
63        """
64        Creating a logger adds a lock to WRITE_LOCKS.
65        """
66        sio = StringIO()
67
68        assert sio not in WRITE_LOCKS
69
70        PrintLogger(sio)
71
72        assert sio in WRITE_LOCKS
73
74    @pytest.mark.parametrize("method", STDLIB_MSG_METHODS)
75    def test_stdlib_methods_support(self, method):
76        """
77        PrintLogger implements methods of stdlib loggers.
78        """
79        sio = StringIO()
80
81        getattr(PrintLogger(sio), method)("hello")
82
83        assert "hello" in sio.getvalue()
84
85
86class TestPrintLoggerFactory(object):
87    def test_does_not_cache(self):
88        """
89        Due to doctest weirdness, we must not re-use PrintLoggers.
90        """
91        f = PrintLoggerFactory()
92
93        assert f() is not f()
94
95    def test_passes_file(self):
96        """
97        If a file is passed to the factory, it get passed on to the logger.
98        """
99        pl = PrintLoggerFactory(sys.stderr)()
100
101        assert sys.stderr is pl._file
102
103    def test_ignores_args(self):
104        """
105        PrintLogger doesn't take positional arguments.  If any are passed to
106        the factory, they are not passed to the logger.
107        """
108        PrintLoggerFactory()(1, 2, 3)
109
110
111class ReturnLoggerTest(object):
112    @pytest.mark.parametrize("method", STDLIB_MSG_METHODS)
113    def test_stdlib_methods_support(self, method):
114        """
115        ReturnLogger implements methods of stdlib loggers.
116        """
117        v = getattr(ReturnLogger(), method)("hello")
118
119        assert "hello" == v
120
121
122class TestReturnLoggerFactory(object):
123    def test_builds_returnloggers(self):
124        f = ReturnLoggerFactory()
125
126        assert isinstance(f(), ReturnLogger)
127
128    def test_caches(self):
129        """
130        There's no need to have several loggers so we return the same one on
131        each call.
132        """
133        f = ReturnLoggerFactory()
134
135        assert f() is f()
136
137    def test_ignores_args(self):
138        """
139        ReturnLogger doesn't take positional arguments.  If any are passed to
140        the factory, they are not passed to the logger.
141        """
142        ReturnLoggerFactory()(1, 2, 3)
143