bug-readline
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Patch] Provide some tests in gtest and shell forms for readline


From: wanghaitao (G)
Subject: [Patch] Provide some tests in gtest and shell forms for readline
Date: Fri, 14 Oct 2022 10:02:45 +0000

I don't see tests directory in readline, there is just some example files.
So I want to write some tests for readline.

I don't know how to write tests like calling it "make test" or "make check"
But I can write tests by gtest and shell.
Here is my tests patch. It add a tests subdirectory in readline.

tests
├── build_readline.sh
├── CMakeLists.txt
├── example_test_script        write by shell
│       ├── history_dup.sh       test examples/histexamp and 
examples/hist_erasedups
│       ├── rlcallbacktest.sh      test examples/rl-callbacktest
│       └── rlkeymaps.sh         test examples/rlkeymaps
├── README.md
├── run_all_tests.sh
└── src                                 write by gtest
        ├── history_test.cpp       test add, modify, stifle, search, read 
history
                └─--readline_test.cpp      test read a line, get previous 
command, clear message

you can call the run_all_tests.sh to compile and run the tests.

By the way, I think my programming level is not so good and it is my first time 
to upload a patch for GNU.
So If you think the code is not good or my spelling is wrong, I apologize for 
that.

Wang Haitao

>From 9c6ec01889f471bce8bd18e96640ee09c596666e Mon Sep 17 00:00:00 2001
From: Wang Haitao <wanghaitao70@huawei.com>
Date: Fri, 14 Oct 2022 11:58:49 +0800
Subject: [PATCH] Add tests files in 2 forms: gtest and shell

Signed-off-by: Wang Haitao <wanghaitao70@huawei.com>
---
tests/CMakeLists.txt                        |  49 ++++++
tests/README.md                             |  32 ++++
tests/build_readline.sh                     |  20 +++
tests/example_test_script/history_dup.sh    |  42 +++++
tests/example_test_script/rlcallbacktest.sh |  38 +++++
tests/example_test_script/rlkeymaps.sh      |   6 +
tests/run_all_tests.sh                      |  20 +++
tests/src/history_test.cpp                  | 166 ++++++++++++++++++++
tests/src/readline_test.cpp                 | 119 ++++++++++++++
9 files changed, 492 insertions(+)
create mode 100644 tests/CMakeLists.txt
create mode 100644 tests/README.md
create mode 100755 tests/build_readline.sh create mode 100755 
tests/example_test_script/history_dup.sh
create mode 100755 tests/example_test_script/rlcallbacktest.sh
create mode 100755 tests/example_test_script/rlkeymaps.sh
create mode 100755 tests/run_all_tests.sh create mode 100644 
tests/src/history_test.cpp create mode 100644 tests/src/readline_test.cpp

diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 
index 0000000..779f240
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,49 @@
+cmake_minimum_required(VERSION 3.14)
+project(readline_test)
+
+# GoogleTest requires at least C++14
+set(CMAKE_CXX_STANDARD 14)
+
+include(FetchContent)
+FetchContent_Declare(
+    googletest
+    GIT_REPOSITORY https://github.com/google/googletest.git
+    GIT_TAG release-1.12.1)
+# For Windows: Prevent overriding the parent project's compiler/linker 
+settings set(gtest_force_shared_crt
+    ON
+    CACHE BOOL "" FORCE)
+FetchContent_MakeAvailable(googletest)
+
+enable_testing()
+
+aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src TEST_SRC) 
+add_executable(readline_tests ${TEST_SRC})
+
+
+# use library in source directory instead of the library that OS 
+provides set(READLINE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) 
+set(READLINE_SHLIB_DIR ${READLINE_DIR}/shlib)
+link_directories(${READLINE_SHLIB_DIR})
+
+# compile and install readline and history before build test project 
+add_custom_target(
+    build_readline
+    COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_readline.sh
+    WORKING_DIRECTORY ${READLINE_DIR}
+    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/build_readline.sh
+    COMMENT "Start to configure and compile readline, please wait for few 
minutes..."
+    VERBATIM)
+add_dependencies(readline_tests build_readline)
+
+# use headers in readline source files
+target_include_directories(readline_tests PRIVATE ${READLINE_DIR})
+
+# readline depend on tinfo
+set(READLINE_LIB ${READLINE_SHLIB_DIR}/libreadline.so 
+${READLINE_SHLIB_DIR}/libhistory.so)
+target_link_libraries(readline_tests ${READLINE_LIB} tinfo)
+
+target_link_libraries(readline_tests GTest::gtest_main)
+
+include(GoogleTest)
+gtest_discover_tests(readline_tests)
diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 
0000000..3621b1d
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,32 @@
+# Description
+I don't see tests directory in readline, there is just some example files.
+So I want to write some tests for readline.
+
+I don't know how to write tests like calling it "make test" or "make check"
+But I can write by gtest and shell.
+
+Here is my tests directory.
+
+```text
+tests
+├── build_readline.sh
+├── CMakeLists.txt
+├── example_test_script     write by shell
+│   ├── history_dup.sh      test examples/histexamp and examples/hist_erasedups
+│   ├── rlcallbacktest.sh   test examples/rl-callbacktest
+│   └── rlkeymaps.sh        test examples/rlkeymaps
+├── README.md
+├── run_all_tests.sh
+└── src                     write by gtest
+    ├── history_test.cpp    test add, modify, stifle, search, read history
+    └── readline_test.cpp   test read a line, get previous command, clear 
message
+```
+
+# Run tests
+build all gtest codes and run scrips then gtest:
+
+```sh
+./run_all_tests.sh
+```
+
+if shell scripts dones't return 1 and gtest report all tests pass, then we are 
good.
\ No newline at end of file
diff --git a/tests/build_readline.sh b/tests/build_readline.sh new file mode 
100755 index 0000000..d81cf4a
--- /dev/null
+++ b/tests/build_readline.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+# If haven't configure then configure
+if [ ! -f "Makefile" ]; then
+    ./configure
+
+    # install readline dependency for using library tinfo in linking.
+    if [ "$(command -v apt)" ]; then
+        sudo apt install -y libncurses5-dev
+    elif [ "$(command -v zypper)" ]; then
+        sudo zypper install -y ncurses-devel
+    fi
+fi
+
+make everything -j "$(nproc)"
+
+pushd shlib
+[[ ! -f libreadline.so ]] && ln -s libreadline.so.* libreadline.so [[ ! 
+-f libhistory.so ]] && ln -s libhistory.so.* libhistory.so popd
diff --git a/tests/example_test_script/history_dup.sh 
b/tests/example_test_script/history_dup.sh
new file mode 100755
index 0000000..7a20a75
--- /dev/null
+++ b/tests/example_test_script/history_dup.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+dir=../../examples
+hist=$dir/histexamp
+dup=$dir/hist_erasedups
+
+cat <<EOT > input
+hello
+apple
+hello
+apple
+hello
+bigbang
+save
+quit
+EOT
+
+$hist < input > /dev/null
+
+# remove last line of input (quit)
+sed -i '$ d' input
+if ! diff input history_file; then
+    echo "failed! history_file diff from input!"
+    exit 1
+fi
+
+
+$dup -t history_file
+
+cat <<EOT > result
+apple
+hello
+bigbang
+save
+EOT
+
+if ! diff result history_file; then
+    echo "failed! history_file diff from result!"
+    exit 1
+fi
+
+rm input result history_file
\ No newline at end of file
diff --git a/tests/example_test_script/rlcallbacktest.sh 
b/tests/example_test_script/rlcallbacktest.sh
new file mode 100755
index 0000000..dc7333c
--- /dev/null
+++ b/tests/example_test_script/rlcallbacktest.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+exe=../../examples/rl-callbacktest
+
+cat <<EOT > input
+hello
+apple
+banana
+lalala
+hahaha
+exit
+EOT
+
+cat <<EOT > result
+rltest$ hello
+input line: hello
+rltest$ apple
+input line: apple
+rltest$ banana
+input line: banana
+rltest$ lalala
+input line: lalala
+rltest$ hahaha
+input line: hahaha
+rltest$ exit
+exit
+rltest: Event loop has exited
+EOT
+
+$exe < input > output
+
+if ! diff output result; then
+    echo "failed! output diff from result!"
+    exit 1
+fi
+
+
+rm input output result
\ No newline at end of file
diff --git a/tests/example_test_script/rlkeymaps.sh 
b/tests/example_test_script/rlkeymaps.sh
new file mode 100755
index 0000000..d41b37a
--- /dev/null
+++ b/tests/example_test_script/rlkeymaps.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+if ! ../../examples/rlkeymaps >/dev/null 2>&1; then
+    echo "failed!"
+    exit 1
+fi
diff --git a/tests/run_all_tests.sh b/tests/run_all_tests.sh new file mode 
100755 index 0000000..6a92c6e
--- /dev/null
+++ b/tests/run_all_tests.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+if [[ ! -f build/readline_tests ]]; then
+    mkdir -p build && pushd build
+    cmake ..
+    make
+    popd
+fi
+
+pushd example_test_script || exit
+for script in ./*; do
+    if [ "$script" != "$0" ]; then
+        $script
+    fi
+done
+popd || exit
+
+pushd build
+./readline_tests
+popd
\ No newline at end of file
diff --git a/tests/src/history_test.cpp b/tests/src/history_test.cpp new file 
mode 100644 index 0000000..a43fc81
--- /dev/null
+++ b/tests/src/history_test.cpp
@@ -0,0 +1,166 @@
+#include "gtest/gtest.h"
+#include "history.h"
+#include "xmalloc.h"
+
+#include <string>
+#include <fstream>
+
+class HistoryTest : public ::testing::Test {
+protected:
+    static const size_t kNum = 7;
+    const char *inputs[kNum] = {"apple", "banana", "candy", "ls", "cd /tmp", 
"touch test", "echo hello > test"};
+    void SetUp() override
+    {
+        using_history();
+        clear_history();
+        unstifle_history();
+    }
+};
+
+HIST_ENTRY *get_latest_history()
+{
+    HIST_ENTRY *hist = history_list()[history_length - 1];
+    EXPECT_TRUE(hist != NULL);
+    return hist;
+}
+
+char *get_history_name(size_t index)
+{
+    auto history = history_list();
+    EXPECT_TRUE(history != NULL);
+    return history[index]->line;
+}
+
+TEST_F(HistoryTest, JustAddHistory)
+{
+    for (size_t i = 0; i < kNum; i++)
+    {
+        add_history(inputs[i]);
+        ASSERT_STREQ(get_history_name(i), inputs[i]);
+        ASSERT_EQ(history_length, i + 1);
+    }
+}
+
+TEST_F(HistoryTest, StifiledHistory)
+{
+    add_history("apple");
+    add_history("banana");
+    add_history("candy");
+    ASSERT_EQ(history_length, 3);
+
+    stifle_history(2);
+    ASSERT_EQ(history_is_stifled(), 1);
+
+    ASSERT_STREQ(get_history_name(0), "banana");
+    ASSERT_STREQ(get_history_name(1), "candy");
+    ASSERT_EQ(history_length, 2);
+
+    add_history("edge");
+    ASSERT_STREQ(get_history_name(0), "candy");
+    ASSERT_STREQ(get_history_name(1), "edge"); }
+
+TEST_F(HistoryTest, SearchHistory)
+{
+    for (size_t i = 0; i < kNum; i++)
+    {
+        add_history(inputs[i]);
+    }
+
+    int pos = history_search_pos("cd", 1, 0);
+    ASSERT_EQ(pos, 4);
+    ASSERT_STREQ(get_history_name(pos), inputs[4]);
+
+    pos = history_search_pos("ba", -1, pos);
+    ASSERT_EQ(pos, 1);
+    ASSERT_STREQ(get_history_name(pos), inputs[1]); }
+
+void CheckHistory(const char *filename, std::string file_should_be) {
+    std::ifstream ifs(filename);
+    std::string read_file((std::istreambuf_iterator<char>(ifs)),
+                          std::istreambuf_iterator<char>());
+    ASSERT_EQ(read_file, file_should_be); }
+
+TEST_F(HistoryTest, ReadHistory)
+{
+    int ret;
+    const char *filename = ".history";
+
+    for (size_t i = 0; i < kNum; i++)
+    {
+        add_history(inputs[i]);
+    }
+
+    ret = write_history(filename);
+    ASSERT_EQ(ret, 0) << strerror(errno);
+
+    clear_history();
+    ASSERT_EQ(history_list()[0], nullptr);
+    ASSERT_EQ(history_length, 0);
+
+    ret = read_history(filename);
+    ASSERT_EQ(ret, 0) << strerror(errno);
+
+    for (size_t i = 0; i < kNum; i++)
+    {
+        ASSERT_STREQ(get_history_name(i), inputs[i]);
+    }
+}
+
+TEST_F(HistoryTest, ModifyHistoryFile)
+{
+    int ret;
+    const char *filename = ".history";
+    const size_t len = 4;
+    std::string file_should_be;
+
+    for (size_t i = 0; i < len; i++)
+    {
+        add_history(inputs[i]);
+        file_should_be.append(inputs[i]);
+        file_should_be.append("\n");
+    }
+
+    ret = write_history(filename);
+    ASSERT_EQ(ret, 0) << strerror(errno);
+    CheckHistory(filename, file_should_be.c_str());
+
+    for (size_t i = len; i < kNum; i++)
+    {
+        add_history(inputs[i]);
+        file_should_be.append(inputs[i]);
+        file_should_be.append("\n");
+    }
+
+    ret = append_history(kNum - len, filename);
+    ASSERT_EQ(ret, 0) << strerror(errno);
+    CheckHistory(filename, file_should_be.c_str());
+
+    ret = history_truncate_file(filename, 0);
+    ASSERT_EQ(ret, 0) << strerror(errno);
+    std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary);
+    ASSERT_EQ(in.tellg(), 0);
+
+    remove(filename);
+}
+
+TEST_F(HistoryTest, TruncateError)
+{
+    int ret;
+    const char *test_dir = ".test_dir";
+
+    ret = 
history_truncate_file("/This/is/a/file/that/does/not/exist/123123#%$#)((DSHF", 
0);
+    ASSERT_NE(ret, 0);
+    ASSERT_EQ(errno, ENOENT);
+
+    mkdir(test_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+    ret = history_truncate_file(test_dir, 0);
+    ASSERT_NE(ret, 0);
+
+    ret = rmdir(test_dir);
+    ASSERT_EQ(ret, 0);
+}
\ No newline at end of file
diff --git a/tests/src/readline_test.cpp b/tests/src/readline_test.cpp new file 
mode 100644 index 0000000..146bb4f
--- /dev/null
+++ b/tests/src/readline_test.cpp
@@ -0,0 +1,119 @@
+#include <cstdio>
+#include <cerrno>
+#include <cstdlib>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "history.h"
+#include "readline.h"
+
+using std::string;
+using testing::internal::CaptureStdout; using 
+testing::internal::GetCapturedStdout;
+
+class ReadlineTest : public ::testing::Test {
+protected:
+    const char *prompt = "Handsome haitao$ ";
+    int pip[2];
+    static const size_t kNum = 7;
+    const char *inputs[kNum] = {"apple", "banana", "candy", "ls", "cd 
+/tmp", "touch test", "echo hello > test"};
+
+    void SetUp() override
+    {
+        // clean readline history and anything else
+        rl_clear_history();
+        rl_clear_signals();
+
+        // Let readline read from pipe intead of stdin
+        ASSERT_EQ(pipe(pip), 0);
+        ASSERT_NE(dup2(pip[0], STDIN_FILENO), -1);
+    }
+
+    // Write to pipe to simulate terminal write
+    void MockTerminalWrite(const char *line)
+    {
+        ssize_t len = write(pip[1], line, strlen(line));
+        ASSERT_NE(len, -1) << strerror(errno);
+    }
+
+    void MockTerminalWrite(char c)
+    {
+        ssize_t len = write(pip[1], &c, sizeof(char));
+        ASSERT_NE(len, -1) << strerror(errno);
+    }
+};
+
+TEST_F(ReadlineTest, JustReadALine)
+{
+    char *line;
+    string output;
+
+    for (size_t i = 0; i < kNum; i++)
+    {
+        CaptureStdout();
+        MockTerminalWrite(inputs[i]);
+        MockTerminalWrite('\n');
+        line = readline(prompt);
+        output = GetCapturedStdout();
+        ASSERT_STREQ(line, inputs[i]);
+        ASSERT_EQ(output, string(prompt) + inputs[i] + "\n");
+        free(line);
+    }
+}
+
+TEST_F(ReadlineTest, PreviousCommands)
+{
+    char *line;
+    std::string output;
+    for (int i = 0; i < kNum; i++)
+    {
+        MockTerminalWrite(inputs[i]);
+        MockTerminalWrite('\n');
+        line = readline(prompt);
+        add_history(line);
+        free(line);
+    }
+
+    for (int i = kNum - 2; i >= 0; i--)
+    {
+        rl_get_previous_history(1, 0);
+        ASSERT_STREQ(rl_line_buffer, inputs[i]);
+    }
+
+    for (int i = 1; i < kNum; i++)
+    {
+        rl_get_previous_history(-1, 0);
+        ASSERT_STREQ(rl_line_buffer, inputs[i]);
+    }
+}
+
+int clear_message(int __, int _)
+{
+    return rl_clear_message();
+}
+
+
+TEST_F(ReadlineTest, JustClearMessage)
+{
+    const char *input = "ls";
+    char * line;
+    string output;
+
+    rl_add_defun("clear_message", clear_message, CTRL('t'));
+
+    CaptureStdout();
+    MockTerminalWrite(input);
+    MockTerminalWrite('\n');
+    line = readline(prompt);
+    output = GetCapturedStdout();
+    ASSERT_EQ(output, string(prompt) + input + "\n");
+    free(line);
+
+    // CaptureStdout();
+    MockTerminalWrite(input);
+    MockTerminalWrite(CTRL('t'));
+    MockTerminalWrite('\n');
+    line = readline(prompt);
+    free(line);
+}
\ No newline at end of file
--
2.17.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]