implement and document new invocation mechanisms, see doc/usage.txt
also rename pytest._core to pytest.main for convenience.
This commit is contained in:
parent
6a734efe44
commit
d108235095
|
@ -2,6 +2,9 @@ Changes between 1.3.4 and 2.0.0dev0
|
|||
----------------------------------------------
|
||||
|
||||
- pytest-2.0 is now its own package and depends on pylib-2.0
|
||||
- new ability: python -m pytest / python -m pytest.main ability
|
||||
- new python invcation: pytest.main(args, plugins) to load
|
||||
some custom plugins early.
|
||||
- try harder to run unittest test suites in a more compatible manner
|
||||
by deferring setup/teardown semantics to the unittest package.
|
||||
- introduce a new way to set config options via ini-style files,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
py.test reference documentation
|
||||
================================================
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
|
|
@ -1,7 +1,19 @@
|
|||
|
||||
pytest builtin helpers
|
||||
py.test builtin helpers
|
||||
================================================
|
||||
|
||||
builtin py.test.* helpers
|
||||
-----------------------------------------------------
|
||||
|
||||
You can always use an interactive Python prompt and type::
|
||||
|
||||
import pytest
|
||||
help(pytest)
|
||||
|
||||
to get an overview on available globally available helpers.
|
||||
|
||||
.. automodule:: pytest
|
||||
:members:
|
||||
|
||||
builtin function arguments
|
||||
-----------------------------------------------------
|
||||
|
@ -54,17 +66,3 @@ You can ask for available builtin or project-custom
|
|||
* ``pop(category=None)``: return last warning matching the category.
|
||||
* ``clear()``: clear list of warnings
|
||||
|
||||
|
||||
builtin py.test.* helpers
|
||||
-----------------------------------------------------
|
||||
|
||||
You can always use an interactive Python prompt and type::
|
||||
|
||||
import pytest
|
||||
help(pytest)
|
||||
|
||||
to get an overview on available globally available helpers.
|
||||
|
||||
.. automodule:: pytest
|
||||
:members:
|
||||
|
||||
|
|
|
@ -58,8 +58,8 @@ builtin configuration file options
|
|||
.. confval:: norecursedirs
|
||||
|
||||
Set the directory basename patterns to avoid when recursing
|
||||
for test discovery. The individual (fnmatch-style) patterns are
|
||||
applied to the basename of a directory to decide if to recurse into it.
|
||||
for test discovery. The individual (fnmatch-style) patterns are
|
||||
applied to the basename of a directory to decide if to recurse into it.
|
||||
Pattern matching characters::
|
||||
|
||||
* matches everything
|
||||
|
@ -68,7 +68,7 @@ builtin configuration file options
|
|||
[!seq] matches any char not in seq
|
||||
|
||||
Default patterns are ``.* _* CVS {args}``. Setting a ``norecurse``
|
||||
replaces the default. Here is a customizing example for avoiding
|
||||
replaces the default. Here is a customizing example for avoiding
|
||||
a different set of directories::
|
||||
|
||||
# content of setup.cfg
|
||||
|
|
|
@ -5,8 +5,8 @@ no-boilerplate testing with Python
|
|||
----------------------------------
|
||||
|
||||
- automatic, fully customizable Python test discovery
|
||||
- :pep:`8` consistent testing style
|
||||
- allows simple test functions
|
||||
- allows fully :pep:`8` compliant coding style
|
||||
- write simple test functions and freely group tests
|
||||
- ``assert`` statement for your assertions
|
||||
- powerful parametrization of test functions
|
||||
- rely on powerful traceback and assertion reporting
|
||||
|
@ -25,8 +25,8 @@ extensive plugin and customization system
|
|||
mature command line testing tool
|
||||
--------------------------------------
|
||||
|
||||
- powerful :ref:`usage` possibilities
|
||||
- used in many projects, ranging from 10 to 10K tests
|
||||
- autodiscovery of tests
|
||||
- simple well sorted command line options
|
||||
- runs on Unix, Windows from Python 2.4 up to Python 3.1 and 3.2
|
||||
- is itself tested extensively on a CI server
|
||||
|
|
|
@ -76,16 +76,21 @@ You can always run your tests by pointing to it::
|
|||
...
|
||||
|
||||
.. _`package name`:
|
||||
|
||||
.. note::
|
||||
|
||||
.. note::
|
||||
|
||||
Test modules are imported under their fully qualified name as follows:
|
||||
|
||||
* ``basedir`` = first upward directory not containing an ``__init__.py``
|
||||
* find ``basedir`` -- this is the first "upward" directory not
|
||||
containing an ``__init__.py``
|
||||
|
||||
* perform ``sys.path.insert(0, basedir)``.
|
||||
* perform ``sys.path.insert(0, basedir)`` to make the fully
|
||||
qualified test module path importable.
|
||||
|
||||
* ``import path.to.test_module``
|
||||
* ``import path.to.test_module`` where the path is determined
|
||||
by converting path separators into "." files. This means
|
||||
you must follow the convention of having directory and file
|
||||
names map to the import names.
|
||||
|
||||
.. _standalone:
|
||||
.. _`genscript method`:
|
||||
|
@ -94,7 +99,7 @@ Generating a py.test standalone Script
|
|||
-------------------------------------------
|
||||
|
||||
If you are a maintainer or application developer and want others
|
||||
to easily run tests you can generate a completely standalone "py.test"
|
||||
to easily run tests you can generate a completely standalone "py.test"
|
||||
script::
|
||||
|
||||
py.test --genscript=runtests.py
|
||||
|
|
|
@ -3,10 +3,14 @@ py.test: no-boilerplate testing with Python
|
|||
|
||||
.. todolist::
|
||||
|
||||
|
||||
.. note::
|
||||
version 2.0 introduces ``pytest`` as the main Python import name
|
||||
but for historic reasons ``py.test`` remains fully valid and
|
||||
represents the same package.
|
||||
|
||||
Welcome to ``py.test`` documentation:
|
||||
|
||||
.. toctree::
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
|
@ -27,4 +31,3 @@ Indices and tables
|
|||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ Overview and Introduction
|
|||
|
||||
features.txt
|
||||
getting-started.txt
|
||||
cmdline.txt
|
||||
usage.txt
|
||||
goodpractises.txt
|
||||
faq.txt
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ However easy_install does not provide an uninstall facility.
|
|||
|
||||
.. IMPORTANT::
|
||||
|
||||
Ensure that you manually delete the init_cov_core.pth file in your site-packages directory.
|
||||
Ensure that you manually delete the init_covmain.pth file in your site-packages directory.
|
||||
|
||||
This file starts coverage collection of subprocesses if appropriate during site initialisation
|
||||
at python startup.
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
|
||||
.. _usage:
|
||||
|
||||
Usage and Invocations
|
||||
==========================================
|
||||
|
||||
|
||||
.. _cmdline:
|
||||
|
||||
Using the interactive command line
|
||||
===============================================
|
||||
|
||||
Getting help on version, option names, environment vars
|
||||
-----------------------------------------------------------
|
||||
|
||||
|
@ -22,6 +25,57 @@ To stop the testing process after the first (N) failures::
|
|||
py.test -x # stop after first failure
|
||||
py.test -maxfail=2 # stop after two failures
|
||||
|
||||
calling pytest from Python code
|
||||
----------------------------------------------------
|
||||
|
||||
.. versionadded: 2.0
|
||||
|
||||
You can invoke ``py.test`` from Python code directly::
|
||||
|
||||
pytest.main()
|
||||
|
||||
this acts as if you would call "py.test" from the command line.
|
||||
It will not raise ``SystemExit`` but return the exitcode instead.
|
||||
You can pass in options and arguments::
|
||||
|
||||
pytest.main(['x', 'mytestdir'])
|
||||
|
||||
or pass in a string::
|
||||
|
||||
pytest.main("-x mytestdir")
|
||||
|
||||
You can specify additional plugins to ``pytest.main``::
|
||||
|
||||
# content of myinvoke.py
|
||||
import pytest
|
||||
class MyPlugin:
|
||||
def pytest_addoption(self, parser):
|
||||
raise pytest.UsageError("hi from our plugin")
|
||||
|
||||
pytest.main(plugins=[MyPlugin()])
|
||||
|
||||
Running it will exit quickly::
|
||||
|
||||
$ python myinvoke.py
|
||||
ERROR: hi from our plugin
|
||||
|
||||
calling pytest through ``python -m pytest``
|
||||
-----------------------------------------------------
|
||||
|
||||
.. versionadded: 2.0
|
||||
|
||||
You can invoke testing through the Python interpreter from the command line::
|
||||
|
||||
python -m pytest.main [...]
|
||||
|
||||
Python2.7 and Python3 introduced specifying packages to "-m" so there
|
||||
you can also type::
|
||||
|
||||
python -m pytest [...]
|
||||
|
||||
All of these invocations are equivalent to the ``py.test [...]`` command line invocation.
|
||||
|
||||
|
||||
Modifying Python traceback printing
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -40,7 +94,7 @@ Dropping to PDB (Python Debugger) on failures
|
|||
|
||||
.. _PDB: http://docs.python.org/library/pdb.html
|
||||
|
||||
Python comes with a builtin Python debugger called PDB_. ``py.test``
|
||||
Python comes with a builtin Python debugger called PDB_. ``py.test``
|
||||
allows to drop into the PDB prompt via a command line option::
|
||||
|
||||
py.test --pdb
|
|
@ -9,8 +9,6 @@ __version__ = '2.0.0.dev18'
|
|||
|
||||
__all__ = ['config', 'cmdline']
|
||||
|
||||
from pytest import _core as cmdline
|
||||
from pytest import main as cmdline
|
||||
UsageError = cmdline.UsageError
|
||||
|
||||
def __main__():
|
||||
raise SystemExit(cmdline.main())
|
||||
main = cmdline.main
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import pytest
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(pytest.main())
|
|
@ -1,4 +1,4 @@
|
|||
""" hook specifications for pytest plugins, invoked from _core.py and builtin plugins. """
|
||||
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Initialization
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
"""
|
||||
pytest PluginManager, basic initialization and tracing.
|
||||
All else is in pytest/plugin.
|
||||
(c) Holger Krekel 2004-2010
|
||||
"""
|
||||
import sys, os
|
||||
import inspect
|
||||
import py
|
||||
from pytest import hookspec
|
||||
from pytest import hookspec # the extension point definitions
|
||||
|
||||
assert py.__version__.split(".")[:2] >= ['2', '0'], ("installation problem: "
|
||||
"%s is too old, remove or upgrade 'py'" % (py.__version__))
|
||||
|
@ -375,23 +380,33 @@ class HookCaller:
|
|||
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
|
||||
return mc.execute()
|
||||
|
||||
pluginmanager = PluginManager(load=True) # will trigger default plugin importing
|
||||
_preinit = [PluginManager(load=True)] # triggers default plugin importing
|
||||
|
||||
def main(args=None):
|
||||
global pluginmanager
|
||||
def main(args=None, plugins=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
hook = pluginmanager.hook
|
||||
elif not isinstance(args, (tuple, list)):
|
||||
args = py.std.shlex.split(str(args))
|
||||
if _preinit:
|
||||
_pluginmanager = _preinit.pop(0)
|
||||
else: # subsequent calls to main will create a fresh instance
|
||||
_pluginmanager = PluginManager(load=True)
|
||||
hook = _pluginmanager.hook
|
||||
try:
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
_pluginmanager.register(plugin)
|
||||
config = hook.pytest_cmdline_parse(
|
||||
pluginmanager=pluginmanager, args=args)
|
||||
pluginmanager=_pluginmanager, args=args)
|
||||
exitstatus = hook.pytest_cmdline_main(config=config)
|
||||
except UsageError:
|
||||
e = sys.exc_info()[1]
|
||||
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
||||
exitstatus = 3
|
||||
pluginmanager = PluginManager(load=True)
|
||||
return exitstatus
|
||||
|
||||
class UsageError(Exception):
|
||||
""" error in py.test usage or invocation"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import py
|
||||
import sys, os
|
||||
from pytest._core import PluginManager
|
||||
from pytest.main import PluginManager
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
funcargs and support code for testing py.test's own functionality.
|
||||
"""
|
||||
|
||||
import py
|
||||
import py, pytest
|
||||
import sys, os
|
||||
import re
|
||||
import inspect
|
||||
|
@ -10,7 +10,7 @@ import time
|
|||
from fnmatch import fnmatch
|
||||
from pytest.plugin.session import Collection
|
||||
from py.builtin import print_
|
||||
from pytest._core import HookRelay
|
||||
from pytest.main import HookRelay
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("pylib")
|
||||
|
@ -401,6 +401,10 @@ class TmpTestdir:
|
|||
#print "env", env
|
||||
return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
|
||||
|
||||
def pytestmain(self, *args, **kwargs):
|
||||
ret = pytest.main(*args, **kwargs)
|
||||
if ret == 2:
|
||||
raise KeyboardInterrupt()
|
||||
def run(self, *cmdargs):
|
||||
return self._run(*cmdargs)
|
||||
|
||||
|
|
|
@ -461,7 +461,7 @@ def hasinit(obj):
|
|||
|
||||
|
||||
def getfuncargnames(function):
|
||||
# XXX merge with _core.py's varnames
|
||||
# XXX merge with main.py's varnames
|
||||
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
|
||||
startindex = py.std.inspect.ismethod(function) and 1 or 0
|
||||
defaults = getattr(function, 'func_defaults',
|
||||
|
|
4
setup.py
4
setup.py
|
@ -46,7 +46,7 @@ def main():
|
|||
)
|
||||
|
||||
def cmdline_entrypoints(versioninfo, platform, basename):
|
||||
target = 'pytest:__main__'
|
||||
target = 'pytest:main'
|
||||
if platform.startswith('java'):
|
||||
points = {'py.test-jython': target}
|
||||
else:
|
||||
|
@ -66,4 +66,4 @@ def make_entry_points():
|
|||
return {'console_scripts': l}
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import sys, py
|
||||
import sys, py, pytest
|
||||
|
||||
class TestGeneralUsage:
|
||||
def test_config_error(self, testdir):
|
||||
|
@ -82,36 +82,6 @@ class TestGeneralUsage:
|
|||
])
|
||||
|
||||
|
||||
def test_earlyinit(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import py
|
||||
assert hasattr(py.test, 'mark')
|
||||
""")
|
||||
result = testdir.runpython(p)
|
||||
assert result.ret == 0
|
||||
|
||||
def test_pydoc(self, testdir):
|
||||
result = testdir.runpython_c("import py;help(py.test)")
|
||||
assert result.ret == 0
|
||||
s = result.stdout.str()
|
||||
assert 'MarkGenerator' in s
|
||||
|
||||
def test_double_pytestcmdline(self, testdir):
|
||||
p = testdir.makepyfile(run="""
|
||||
import py
|
||||
py.test.cmdline.main()
|
||||
py.test.cmdline.main()
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpython(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
|
||||
@py.test.mark.xfail
|
||||
def test_early_skip(self, testdir):
|
||||
|
@ -225,19 +195,6 @@ class TestGeneralUsage:
|
|||
"*1 pass*",
|
||||
])
|
||||
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_python_minus_m_invocation_ok(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_hello(): pass")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
|
||||
assert res.ret == 0
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_python_minus_m_invocation_fail(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_fail(): 0/0")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
|
||||
assert res.ret == 1
|
||||
|
||||
def test_skip_on_generated_funcarg_id(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
import py
|
||||
|
@ -253,3 +210,83 @@ class TestGeneralUsage:
|
|||
res = testdir.runpytest(p)
|
||||
assert res.ret == 0
|
||||
res.stdout.fnmatch_lines(["*1 skipped*"])
|
||||
|
||||
class TestInvocationVariants:
|
||||
def test_earlyinit(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import py
|
||||
assert hasattr(py.test, 'mark')
|
||||
""")
|
||||
result = testdir.runpython(p)
|
||||
assert result.ret == 0
|
||||
|
||||
def test_pydoc(self, testdir):
|
||||
result = testdir.runpython_c("import py;help(py.test)")
|
||||
assert result.ret == 0
|
||||
s = result.stdout.str()
|
||||
assert 'MarkGenerator' in s
|
||||
|
||||
def test_double_pytestcmdline(self, testdir):
|
||||
p = testdir.makepyfile(run="""
|
||||
import py
|
||||
py.test.cmdline.main()
|
||||
py.test.cmdline.main()
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpython(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_python_minus_m_invocation_ok(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_hello(): pass")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
|
||||
assert res.ret == 0
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_python_minus_m_invocation_fail(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_fail(): 0/0")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
|
||||
assert res.ret == 1
|
||||
|
||||
def test_python_pytest_main(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_pass(): pass")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "pytest.main", str(p1))
|
||||
assert res.ret == 0
|
||||
res.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,7)")
|
||||
def test_python_pytest_package(self, testdir):
|
||||
p1 = testdir.makepyfile("def test_pass(): pass")
|
||||
res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
|
||||
assert res.ret == 0
|
||||
res.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
def test_equivalence_pytest_pytest(self):
|
||||
assert pytest.main == py.test.cmdline.main
|
||||
|
||||
def test_invoke_with_string(self, capsys):
|
||||
retcode = pytest.main("-h")
|
||||
assert not retcode
|
||||
out, err = capsys.readouterr()
|
||||
assert "--help" in out
|
||||
|
||||
def test_invoke_with_path(self, testdir, capsys):
|
||||
retcode = testdir.pytestmain(testdir.tmpdir)
|
||||
assert not retcode
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
def test_invoke_plugin_api(self, capsys):
|
||||
class MyPlugin:
|
||||
def pytest_addoption(self, parser):
|
||||
parser.addoption("--myopt")
|
||||
|
||||
pytest.main(["-h"], plugins=[MyPlugin()])
|
||||
out, err = capsys.readouterr()
|
||||
assert "--myopt" in out
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import py
|
|||
|
||||
import pytest.plugin
|
||||
plugindir = py.path.local(pytest.plugin.__file__).dirpath()
|
||||
from pytest._core import default_plugins
|
||||
from pytest.main import default_plugins
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
if path.basename.startswith("pytest_") and path.ext == ".py":
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import py
|
||||
import os, sys
|
||||
from pytest.plugin.pytester import LineMatcher, LineComp, HookRecorder
|
||||
from pytest._core import PluginManager
|
||||
from pytest.main import PluginManager
|
||||
|
||||
def test_reportrecorder(testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
|
@ -97,7 +97,7 @@ def test_hookrecorder_basic_no_args_hook():
|
|||
def test_functional(testdir, linecomp):
|
||||
reprec = testdir.inline_runsource("""
|
||||
import py
|
||||
from pytest._core import HookRelay, PluginManager
|
||||
from pytest.main import HookRelay, PluginManager
|
||||
pytest_plugins="pytester"
|
||||
def test_func(_pytest):
|
||||
class ApiClass:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import py, os
|
||||
from pytest._core import PluginManager, canonical_importname
|
||||
from pytest._core import MultiCall, HookRelay, varnames
|
||||
from pytest.main import PluginManager, canonical_importname
|
||||
from pytest.main import MultiCall, HookRelay, varnames
|
||||
|
||||
|
||||
class TestBootstrapping:
|
||||
|
@ -552,7 +552,7 @@ class TestHookRelay:
|
|||
|
||||
class TestTracer:
|
||||
def test_simple(self):
|
||||
from pytest._core import TagTracer
|
||||
from pytest.main import TagTracer
|
||||
rootlogger = TagTracer()
|
||||
log = rootlogger.get("pytest")
|
||||
log("hello")
|
||||
|
@ -566,7 +566,7 @@ class TestTracer:
|
|||
assert l[1] == "[pytest:collection] hello\n"
|
||||
|
||||
def test_setprocessor(self):
|
||||
from pytest._core import TagTracer
|
||||
from pytest.main import TagTracer
|
||||
rootlogger = TagTracer()
|
||||
log = rootlogger.get("1")
|
||||
log2 = log.get("2")
|
||||
|
@ -588,7 +588,7 @@ class TestTracer:
|
|||
|
||||
|
||||
def test_setmyprocessor(self):
|
||||
from pytest._core import TagTracer
|
||||
from pytest.main import TagTracer
|
||||
rootlogger = TagTracer()
|
||||
log = rootlogger.get("1")
|
||||
log2 = log.get("2")
|
Loading…
Reference in New Issue