将 Python 的标准输出传递给写入的 C 函数

Pass Python's stdout to a C function to which write

我想对打印到 stdout 的 C 函数进行单元测试,但在我搜索之后,我到达了 os.dupos.pip 和其他不是捕获 stdout 的最酷方式的东西C共享库函数。所以我想在它写入后将 Python 的标准输出传递给 C 函数,然后我可以获得要测试的值,但它不起作用。 这是代码:

compile the file as a shared library: gcc -shared -Wl,-soname,tomat -o tomat.so -fPIC tomat.c

/* filename: tomat.c */
#include <stdio.h>
#include <unistd.h>

int
tomat(int length, FILE *stdout_)
{
    int remain = 0;
    while ((remain = (length -= 1)) != 0)
    {
        fprintf(stdout_, "00:%d\n", remain);
        sleep(1);
    }
    return 0;
}

# filename: tomat_test.py
import sys
import ctypes
import unittest

class TomatTestCase(unittest.TestCase):
    def setUp(self):
        self.lib = ctypes.CDLL('./tomat.so')

    def test_prints_every_second(self):
        seconds = ['00:1', '00:2', '00:2', '00:3', '00:4', '00:5',
                   '00:6', '00:7', '00:8', '00:9']
        self.lib.tomat(10, sys.stdout.fileno())
        self.assertEqual(output, seconds[::-1])

希望对其他人有所帮助。

import os
import sys
from tempfile import TemporaryFile
from io import TextIOWrapper, SEEK_SET
from contextlib import contextmanager
from ctypes import c_void_p


@contextmanager
def capture(stream, libc):
    osfd = sys.stdout.fileno()
    fd = os.dup(osfd)
    try:
        tfile = TemporaryFile(mode='w+b')
        redirect_stdout(tfile.fileno(), osfd, libc)
        yield
        redirect_stdout(fd, osfd, libc)
        tfile.flush()
        tfile.seek(0, SEEK_SET)
        stream.write(tfile.read())
    finally:
        tfile.close()
        os.close(fd)


def redirect_stdout(fd, osfd, libc):
    libc.fflush(c_void_p.in_dll(libc, 'stdout'))
    sys.stdout.close()
    os.dup2(fd, osfd)
    sys.stdout = TextIOWrapper(os.fdopen(osfd, 'wb'))

我如何使用它来测试定时器功能的输出:

from io import BytesIO
from ctypes import CDLL
from unittest import TestCase
from helpers import capture


class TomatTestCase(TestCase):
    def setUp(self):
        self.libc = CDLL('./tomat.so')
        self.maxDiff = None

    def test_prints_every_second(self):
        seconds = [
            '01:00', '01:01', '01:02', '01:03', '01:04', '01:05', '01:06',
            '01:07', '01:08', '01:09', '01:10', '01:11', '01:12', '01:13',
            '01:14', '01:15', '01:16', '01:17', '01:18', '01:19', '01:20',
            '01:21', '01:22', '01:23', '01:24', '01:25', '01:26', '01:27',
            '01:28', '01:29', '01:30', '01:31', '01:32', '01:33', '01:34',
            '01:35', '01:36', '01:37', '01:38', '01:39', '01:40', '01:41',
            '01:42', '01:43', '01:44', '01:45', '01:46', '01:47', '01:48',
            '01:49', '01:50', '01:51', '01:52', '01:53', '01:54', '01:55',
            '01:56', '01:57', '01:58', '01:59', '01:60']

        stream = BytesIO()
        with capture(stream, self.libc):
            self.libc.tomat(2)
        stream = stream.getvalue().split()
        output = [byte.decode() for byte in stream]

        self.assertListEqual(output, seconds)

有关其工作原理的更多信息,您可以查看 Eli Bendersky 的 post:http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/