从分段输出文件中提取第一行和最后一行的算法

Algorithm for extracting first and last lines from sectionalized output file

我试图从 Pytest 会话的终端输出中逐行解析 FAILURES 部分,识别每个测试的测试名称和测试文件名,然后我想将它们附加在一起以形成“完全合格的测试”名称”(FQTN),例如tests/test_1.py::test_3_fails。我还想获取并保存回溯信息(测试名称和测试文件名之间的信息)。

解析部分很简单,我已经有了匹配测试名称和测试文件名的工作正则表达式,我可以基于它提取回溯信息。我的 FQTN 问题是算法 - 我似乎无法弄清楚识别测试名称的整体逻辑,然后是测试的文件名,wihch 出现在后面的一行。我不仅需要适应 FAILURES 部分中间的测试,还需要适应 FAILURES 部分的第一个测试和最后一个测试。

举个例子。这是测试期间所有失败的输出部分 运行,以及一些出现在失败之前和之后的终端输出。

.
.
.
============================================== ERRORS ===============================================
__________________________________ ERROR at setup of test_c_error ___________________________________

    @pytest.fixture
    def error_fixture():
>       assert 0
E       assert 0

tests/test_2.py:19: AssertionError
============================================= FAILURES ==============================================
___________________________________________ test_3_fails ____________________________________________

log_testname = None

    def test_3_fails(log_testname):
>       assert 0
E       assert 0

tests/test_1.py:98: AssertionError
--------------------------------------- Captured stdout setup ---------------------------------------
Running test tests.test_1...
Running test tests.test_1...
Setting test up...
Setting test up...
Executing test...
Executing test...
Tearing test down...
Tearing test down...
---------------------------------------- Captured log setup -----------------------------------------
INFO     root:test_1.py:68 Running test tests.test_1...
INFO     root:test_1.py:69 Setting test up...
INFO     root:test_1.py:70 Executing test...
INFO     root:test_1.py:72 Tearing test down...
______________________________________ test_8_causes_a_warning ______________________________________

log_testname = None

    def test_8_causes_a_warning(log_testname):
>       assert api_v1() == 1
E       TypeError: api_v1() missing 1 required positional argument: 'log_testname'

tests/test_1.py:127: TypeError
--------------------------------------- Captured stdout setup ---------------------------------------
Running test tests.test_1...
Running test tests.test_1...
Setting test up...
Setting test up...
Executing test...
Executing test...
Tearing test down...
Tearing test down...
---------------------------------------- Captured log setup -----------------------------------------
INFO     root:test_1.py:68 Running test tests.test_1...
INFO     root:test_1.py:69 Setting test up...
INFO     root:test_1.py:70 Executing test...
INFO     root:test_1.py:72 Tearing test down...
___________________________ test_16_fail_compare_dicts_for_pytest_icdiff ____________________________

    def test_16_fail_compare_dicts_for_pytest_icdiff():
        listofStrings = ["Hello", "hi", "there", "at", "this"]
        listofInts = [7, 10, 45, 23, 77]
        assert len(listofStrings) == len(listofInts)
>       assert listofStrings == listofInts
E       AssertionError: assert ['Hello', 'hi... 'at', 'this'] == [7, 10, 45, 23, 77]
E         At index 0 diff: 'Hello' != 7
E         Full diff:
E         - [7, 10, 45, 23, 77]
E         + ['Hello', 'hi', 'there', 'at', 'this']

tests/test_1.py:210: AssertionError
____________________________________________ test_b_fail ____________________________________________

    def test_b_fail():
>       assert 0
E       assert 0

tests/test_2.py:27: AssertionError
============================================== PASSES ===============================================
___________________________________________ test_4_passes ___________________________________________
--------------------------------------- Captured stdout setup ---------------------------------------
Running test tests.test_1...
Running test tests.test_1...
Setting test up...
Setting test up...
Executing test...
Executing test...
Tearing test down...
Tearing test down...
.
.
.

这里有人精通算法吗,也许是一些伪代码,显示了获取每个测试名称及其相关测试文件名的总体方法?

这是我为测试用例报告获取呈现摘要的建议。使用这个存根作为一个粗略的想法 - 您可能想要遍历报告并首先转储呈现的摘要,然后使用 curses 魔法来显示收集的数据。

一些测试:

import pytest


def test_1():
    assert False


def test_2():
    raise RuntimeError('call error')


@pytest.fixture
def f():
    raise RuntimeError('setup error')


def test_3(f):
    assert True


@pytest.fixture
def g():
    yield
    raise RuntimeError('teardown error')


def test_4(g):
    assert True

呈现 test_3 案例摘要的虚拟插件示例。将片段放入 conftest.py:

def pytest_unconfigure(config):
    # example: get rendered output for test case `test_spam.py::test_3`

    # get the reporter
    reporter = config.pluginmanager.getplugin('terminalreporter')

    # create a buffer to dump reporter output to
    import io

    buf = io.StringIO()
    # fake tty or pytest will not colorize the output
    buf.isatty = lambda: True

    # replace writer in reporter to dump the output in buffer instead of stdout
    from _pytest.config import create_terminal_writer

    # I want to use the reporter again later to dump the rendered output,
    # so I store the original writer here (you probably don't need it)
    original_writer = reporter._tw
    writer = create_terminal_writer(config, file=buf)
    # replace the writer
    reporter._tw = writer

    # find the report for `test_spam.py::test_3` (we already know it will be an error report)
    errors = reporter.stats['error']
    test_3_report = next(
        report for report in errors if report.nodeid == 'test_spam.py::test_3'
    )

    # dump the summary along with the stack trace for the report of `test_spam.py::test_3`
    reporter._outrep_summary(test_3_report)

    # print dumped contents
    # you probably don't need this - this is just for demo purposes

    # restore the original writer to write to stdout again
    reporter._tw = original_writer
    reporter.section('My own section', sep='>')
    reporter.write(buf.getvalue())
    reporter.write_sep('<')

A pytest 运行 现在产生一个额外的部分

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> My own section >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    @pytest.fixture
    def f():
>       raise RuntimeError('setup error')
E       RuntimeError: setup error

test_spam.py:14: RuntimeError
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

堆栈跟踪的渲染方式与 pytestERRORS 摘要部分中的渲染方式相同。如果需要,您可以使用不同测试用例的结果 - 如有必要,替换 reporter.stats 部分(errorsfailed,甚至 passed - 尽管摘要应该是空的对于通过的测试)并修改测试用例 nodeid.