Получить последнюю строку, напечатанную в C/C++

Я пытаюсь создать тестер с помощью googletest. проблема в том, что функция, которую я тестирую, возвращает void и вместо этого печатает результат. Я хочу, чтобы последняя строка была напечатана в консоли, чтобы я мог проверить вывод. строка может включать \n.

поэтому у меня есть сама функция:

void f_sequence(char sequenceStr[])
{
   //logic...
    if(condotion1)
        printf("somthing1");
    else if(condotion2)
        printf("somthing2")
(...)
}

а потом тестер:

TEST(TesterGroup, TesterName)
{
    f_sequence("input");
    EXPECT_EQ("somthing1", /*how do i get the output?*/);
}

Является ли это возможным?

Функции, которые я тестирую, написаны на C, а сама функция Test (тестер) — на C++. вывод печатается с использованием printf. Я не могу изменить саму функцию. Я использую последнюю версию CLion.


person avivgood2    schedule 19.02.2020    source источник
comment
Можете ли вы изменить функцию, чтобы она принимала параметр std::ostream& вместо того, чтобы просто печатать в stdout/stderr?   -  person jtbandes    schedule 19.02.2020
comment
с или с++? Это разные языки, и решения будут существенно отличаться. (и нет языка под названием C/C++, это, к сожалению, часто используемое неправильное название)   -  person 463035818_is_not_a_number    schedule 19.02.2020
comment
@idclev463035818 idclev463035818 Функции, которые я тестирую, написаны на c, а сама тестовая функция (тестер) — на c++   -  person avivgood2    schedule 19.02.2020
comment
@avivgood2 - опубликуйте некоторый (минимальный) код, демонстрирующий проблему.   -  person Happy Green Kid Naps    schedule 19.02.2020
comment
@jtbandes Он печатается с помощью printf, и я не могу его изменить, просто напишите тестеры   -  person avivgood2    schedule 19.02.2020
comment
не моя тема, чтобы помочь, но все же вы должны предоставить минимальный воспроизводимый пример. Детали имеют значение   -  person 463035818_is_not_a_number    schedule 19.02.2020
comment
Вы можете перенаправить стандартный вывод в файл, а затем прочитать из этого файла, когда ваша функция C вернется.   -  person Ruslan    schedule 19.02.2020
comment
Эта функция не тестируется как есть. Я бы порекомендовал предложение @jtbandes.   -  person andre    schedule 19.02.2020
comment
Исходный код ЗДЕСЬ (только для приложений Windows), который создает API для отправки cmd и чтения stdout в буфер с динамическим размером. Я протестировал его до 2 ГБ при чтении рекурсивного вызова каталога в Windows. ( Вот оригинал, перед просмотром)   -  person ryyker    schedule 19.02.2020


Ответы (5)


Перенаправить стандартный вывод в буфер.

Прямая трансляция на Coliru

#include <stdio.h>
#include <unistd.h>

#define BUFFER_SIZE 1024
int stdoutSave;
char outputBuffer[BUFFER_SIZE];

void replaceStdout()
{
    fflush(stdout); //clean everything first
    stdoutSave = dup(STDOUT_FILENO); //save the stdout state
    freopen("NUL", "a", stdout); //redirect stdout to null pointer
    setvbuf(stdout, outputBuffer, _IOFBF, 1024); //set buffer to stdout
}

void restoreStdout()
{
    freopen("NUL", "a", stdout); //redirect stdout to null again
    dup2(stdoutSave, STDOUT_FILENO); //restore the previous state of stdout
    setvbuf(stdout, NULL, _IONBF, BUFFER_SIZE); //disable buffer to print to screen instantly
}

void printHelloWorld()
{
    printf("hello\n");
    printf("world");
}

int main()
{
    replaceStdout();
    printHelloWorld();
    restoreStdout();
    // Use outputBuffer to test EXPECT_EQ("somthing1", outputBuffer);
    printf("Fetched output: (%s)", outputBuffer);
    return 0;
}

Ссылки: http://kaskavalci.com/redirecting-stdout-to-array-and-restoring-it-back-in-c/

person LWolf    schedule 19.02.2020

Я не знаю, возможно ли получить то, что было напечатано последним, но если вы управляете средой до вызова вашей тестовой функции, вы можете перенаправить туда, куда идет стандартный вывод, что позволит вам записать его в файл, который вы затем можете проверить.

См. этот старый ответ, которым ИМО пренебрегали. Пример из него изменен здесь:

FILE *fp_old = stdout;  // preserve the original stdout
stdout = fopen("/path/to/file/you/want.txt","w");  // redirect stdout to anywhere you can open
// CALL YOUR FUNCTION UNDER TEST HERE
fclose(stdout);  // Close the file with the output contents
stdout=fp_old;  // restore stdout to normal

// Re-open the file from above, and read it to make sure it contains what you expect.
person Kevin Anderson    schedule 19.02.2020
comment
Почему бы не просто freopen? - person Ruslan; 19.02.2020
comment
@Ruslan Я не уверен, сохранит ли freopen() исходный стандартный поток вывода или он действительно закроет стандартный вывод, и вы не сможете вернуть его (легко), что в среде тестирования, вероятно, плохо. - person Kevin Anderson; 19.02.2020
comment
@KevinAnderson, использующий stdout в качестве l-значения, не является переносимым ... - person CoffeeTableEspresso; 19.02.2020
comment
Справедливо. Более подробное обсуждение вариантов для этого типа вещей здесь: c-faq.com/stdio/undofreopen.html - person Kevin Anderson; 19.02.2020

Два пути:

Если вы работаете в системе, совместимой с POSIX, вы можете перенаправить вывод программы в файл с помощью >, а затем прочитать из файла позже, чтобы убедиться, что вывод правильный.

Другой способ примерно такой:

freopen("output.txt", "w", stdout);
f_sequence();
freopen("/dev/tty", "w", stdout);

для POSIX-совместимых систем. На других платформах вам нужно изменить "/dev/tty" на что-то другое. Я не знаю полностью портативного способа сделать это.

А потом читать с output.txt. Вышеприведенный фрагмент изменяет то, что такое stdout, так что он печатает в файл вместо обычного стандартного вывода.

person CoffeeTableEspresso    schedule 19.02.2020
comment
Почему бы не просто freopen? - person Ruslan; 19.02.2020
comment
На самом деле, я не думаю, что вы можете сохранить исходный stdout таким образом... freopen изменит указатель. - person Ruslan; 19.02.2020
comment
@Руслан ты прав. Однако я не уверен, что это 100% портативный способ сделать это. - person CoffeeTableEspresso; 19.02.2020
comment
Что касается первого варианта, как я могу перенаправить вывод в файл? (Я использую CLion). и как я могу прочитать из выходного файла через код? - person avivgood2; 19.02.2020
comment
Я бы порекомендовал второй вариант, поскольку первый включает в себя компиляцию вашей функции отдельно и ее выполнение (если я что-то не упустил). - person CoffeeTableEspresso; 19.02.2020
comment
Оооооо, тогда не так весело, лол. Используйте второй способ, первый способ не будет работать в Windows. - person CoffeeTableEspresso; 19.02.2020
comment
Какой компилятор вы используете? - person CoffeeTableEspresso; 19.02.2020
comment
@CoffeeTableEspresso во втором варианте, где я беру строку? в какую переменную? - person avivgood2; 19.02.2020
comment
вывод, который обычно печатается с помощью printf, вместо этого помещается в файл с именем output.txt. Вы только что прочитали из этого файла... - person CoffeeTableEspresso; 19.02.2020
comment
если файл сохранен в output.txt, то для чего нужна строка freopen("/dev/tty", "w", stdout);? (извините за несколько вопросов, я просто пытаюсь понять код) - person avivgood2; 19.02.2020
comment
@ avivgood2, то есть восстановить stdout до его старого значения после того, как вы закончите. Поскольку вы работаете в Windows, вам, вероятно, не нужен /dev/tty. Не знаю, что такое Windows... - person CoffeeTableEspresso; 19.02.2020

Одно решение: вы можете написать отдельную программу, которая выполняет функцию, а в модульном тесте вы можете выполнить эту программу как подпроцесс и проверить вывод. Это можно сделать с помощью std::system, но будьте очень осторожны, чтобы не передавать ему непостоянные входные данные. Вам не нужна уязвимость внедрения оболочки даже в модульном тесте. Существуют специальные системные функции, которые позволяют избежать использования оболочки в подпроцессе.

Другое решение, которое возможно, по крайней мере, на POSIX: заменить стандартные потоки out / err файловыми дескрипторами, а затем прочитать файлы.

Специфика Googletest: кажется, есть testing::internal::CaptureStdout, который реализует идею замены стандартных потоков. Но, как следует из пространства имен, это не официальный API, поэтому в будущем он может измениться.

person eerorika    schedule 19.02.2020
comment
Я и Кевин оба уже предложили оба этих варианта... - person CoffeeTableEspresso; 19.02.2020
comment
system(3) — это стандартный способ выполнения подпроцессов. - person Ruslan; 19.02.2020
comment
@Ruslan Я научился игнорировать это из-за присущей ему небезопасности. Но я полагаю, что это не критично здесь, пока нет пользовательского ввода. - person eerorika; 19.02.2020
comment
@CoffeeTableEspresso Поздравляю, вы быстро печатаете. - person eerorika; 19.02.2020

Существует решение (в C) для вызова API (cmd_rsp) с исходным кодом здесь, который при вызове в вашей программе создает отдельный процесс и передает его как stdin, так и stdout, от которых он принимает команду и возвращает ответ через буфер автоматического изменения размера. По концепции похож на popen(...).

Простой вариант использования:

char *buf = NULL;

/// test cmd_rsp
buf = calloc(BUF_SIZE, 1);
if(!buf)return 0;
if (!cmd_rsp("dir /s", &buf, BUF_SIZE))//note the first argument can be any legal command that 
                                       //can be sent via the CMD prompt in windows, 
                                       //including a custom executable
{
    printf("%s", buf);
}
else
{
    printf("failed to send command.\n");
}
free(buf);
person ryyker    schedule 19.02.2020