【问题标题】:Loading test sequence file in parameterised Google Test在参数化的 Google Test 中加载测试序列文件
【发布时间】:2018-11-28 08:17:42
【问题描述】:

我正在尝试在 Google Test 中加载测试序列。我有几个测试序列,所以我正在尝试进行参数化测试,将目录带到测试序列(多个文件),但是当我尝试释放资源时,我在析构函数中遇到分段错误。

// Test sequence class
class TestSequence {
 public:
    TestSequence(const std::string& dir)
        : TestSequence(dir + "/Values1.csv", dir + "/Values2.csv",
                       dir + "/Values3.csv") {}

    TestSequence(const std::string& val1_file, const std::string& val2_file,
                 const std::string& val3_file)
        : val1_file_path(val1_file),
          val2_file_path(val2_file),
          val3_file_path(val3_file) {
        mp_val1_file = new std::ifstream(m_val1_file_path);
        mp_val2_file = new std::ifstream(m_val2_file_path);
        mp_val3_file = new std::ifstream(m_val3_file_path);
    }

    virtual ~TestSequence() {
        delete mp_flows_file;  // <- Segmentation fault
        delete mp_pres_file;
        delete mp_params_file;
    }

    bool NextValue(MyValueType * p_value) {
        // Do some parsing on the file
        ...
    }
 private:
    std::string val1_file_path;
    std::string val2_file_path;
    std::string val3_file_path;

    std::ifstream *mp_val1_file;
    std::ifstream *mp_val1_file;
    std::ifstream *mp_val1_file;
}

// Test case class
class AlgorithmTests
    : public testing::TestWithParam<TestSequence> {
 protected:
    // Unit under test, mocks, etc...

 public:
    VentilationDetectionAlgorithmTests(void) {
        // Setting up unit under tests...
    }
};


// Instantiate parameterised tests with paths to directories
INSTANTIATE_TEST_CASE_P(
    SomeSequences, AlgorithmTests,
    ::testing::Values(TestSequence("test/support/sequence1"),
                      TestSequence("test/support/sequence2")));

我写了两个测试。我在测试序列的构造函数和析构函数以及每个测试的第一行添加了一个断点。结果如下:

  1. 为每个目录调用一次序列构造函数(预期)
  2. 为每个目录调用一次序列析构函数,以相反的顺序(意外)
  3. 在最后一个目录上再次调用序列析构函数(delete 上的分段错误)

从未达到测试。

  • 我尝试在删除变量后将其设置为nullptr,并在删除前检查它,但没有帮助。
  • 如果我替换指向 ifstreams 的指针,我会收到编译错误(错误:调用 'TestSequence' 的隐式删除的复制构造函数

我认为我误解了 Google Test 如何使用创建的参数,或者我应该如何处理 C++ 中的资源。

感谢您对此的任何意见!

堆栈跟踪:

test.out!TestSequence::~TestSequence()
(/path/to/project/test/test_Algorithm.cpp:60)
test.out!TestSequence::~TestSequence() (/path/to/project/test/test_Algorithm.cpp:58)
test.out!testing::internal::ValueArray2<TestSequence, TestSequence>::operator testing::internal::ParamGenerator<TestSequence><TestSequence>() const (/path/to/project/vendor/googletest/include/gtest/internal/gtest-param-util-generated.h:103)
test.out!gtest_LongSequencesAlgorithmTests_EvalGenerator_() (/path/to/project/test/test_Algorithm.cpp:170)
test.out!testing::internal::ParameterizedTestCaseInfo<AlgorithmTests>::RegisterTests() (/path/to/project/vendor/googletest/include/gtest/internal/gtest-param-util.h:554)
test.out!testing::internal::ParameterizedTestCaseRegistry::RegisterTests() (/path/to/project/vendor/googletest/include/gtest/internal/gtest-param-util.h:714)
test.out!testing::internal::UnitTestImpl::RegisterParameterizedTests() (/path/to/project/vendor/googletest/src/gtest.cc:2620)
test.out!testing::internal::UnitTestImpl::PostFlagParsingInit() (/path/to/project/vendor/googletest/src/gtest.cc:4454)
test.out!void testing::internal::InitGoogleTestImpl<char>(int*, char**) (/path/to/project/vendor/googletest/src/gtest.cc:5356)
test.out!testing::InitGoogleTest(int*, char**) (/path/to/project/vendor/googletest/src/gtest.cc:5374)
test.out!void testing::internal::InitGoogleMockImpl<char>(int*, char**) (/path/to/project/vendor/googlemock/src/gmock.cc:131)
test.out!testing::InitGoogleMock(int*, char**) (/path/to/project/vendor/googlemock/src/gmock.cc:174)
test.out!main (/path/to/project/test/test_Main.cpp:13)
libdyld.dylib!start (Unknown Source:0)
libdyld.dylib!start (Unknown Source:0)

【问题讨论】:

    标签: c++ googletest


    【解决方案1】:

    您获得 SIGSEGV 是因为您“复制”了指针的值(例如 0x123123 地址)并生成了double free。因此,即使您设置为 nullptr 也无济于事,因为您的 TestSequence 的其他副本会记住旧地址。

    如果你想避免这种情况,你应该覆盖你班级的 copy ctorassign copy operator 的隐式版本。最好的解决方案是使用std::unique_ptr

    一些信息about implicit stuff

    例子:

    #include <iostream>
    #include <memory>
    #include <string>
    #include <fstream>
    
    class TestSequence {
    public:
        TestSequence(const std::string& val1_file)
            : val1_file_path(val1_file)
        {
            mp_val1_file = std::make_unique<std::ifstream>(val1_file_path);
        }
    
        TestSequence(const TestSequence& other)
            : val1_file_path(other.val1_file_path)
        {
            mp_val1_file = std::make_unique<std::ifstream>(val1_file_path);
        }
    
        TestSequence(TestSequence&& other) = default; // We want default one ;)
        // Other alternative implementation of above
        /*TestSequence(const TestSequence& other)
                : val1_file_path(other.val1_file_path)
            {
                mp_val1_file = std::make_unique<std::ifstream>(val1_file_path);
            }*/
    
    
        TestSequence& operator=(const TestSequence& other)
        {
            val1_file_path = other.val1_file_path;
            mp_val1_file = std::make_unique<std::ifstream>(val1_file_path);
            return *this;
        }
    
        TestSequence& operator=(TestSequence&& other) = default; 
        // Other alternative implementation of above
        /*TestSequence& operator=(TestSequence&& other) // move semantics from C++11
        {
            val1_file_path = std::move(other.val1_file_path);
            mp_val1_file = std::move(other.mp_val1_file);
            return *this;
        }*/
    
     private:
        std::string val1_file_path;
        std::unique_ptr<std::ifstream> mp_val1_file;
    };
    

    在这个实现中,我使用了智能指针,例如:std::unique_ptr。可以肯定的是,我明确表示我想要默认的移动语义operator= 和移动 ctor(也许它们会默认生成但不确定,所以我更喜欢显式标记)。这取决于您的用例,您希望如何实现复制 ctor 和复制分配。在我的情况下,我重新打开缓冲区,但也许您还想复制缓冲区的位置或其他东西。只需创建新的 ifstream。


    其他解决方案是:(不推荐)

    class TestSequence {
    public:
        TestSequence(const std::string& val1_file)
            : val1_file_path(val1_file)
        {
            mp_val1_file = new std::ifstream(val1_file_path);
        }
    
        TestSequence(const TestSequence& other)
            : val1_file_path(other.val1_file_path)
        {
            mp_val1_file = new std::ifstream(val1_file_path);
        }
    
        ~TestSequence()
        {
            delete mp_val1_file;
            mp_val1_file = nullptr;
        }
    
        TestSequence& operator=(const TestSequence& other)
        {
            val1_file_path = other.val1_file_path;
            mp_val1_file = new std::ifstream(val1_file_path);
            return *this;
        }
    
     private:
        std::string val1_file_path;
        std::ifstream* mp_val1_file = nullptr;
    };
    

    代码说明:

    当你创建TestSequence的实例时

    TestSequence mySequence("my/magic/path");
    // mySequence = TestSequence{file_path="my/magic/path", input_stream=0x123123} (example)
    // Now if you write such thing
    TestSequence copy = mySequence; // same as TestSequence copy(mySequence);
    // copy = TestSequence{file_path="my/magic/path", input_stream=0x123123} <--- same address because of default copy constructor
    

    现在如果 mySequence 会死,例如。它只是一个参数,我们称之为析构函数,所以它看起来像:

    // mySequence = TestSequence{file_path="my/magic/path", input_stream=0x0}
    // copy = TestSequence{file_path="my/magic/path", input_stream=0x123123}
    

    如您所见,mySequence 将释放input_stream 指针下的数据,但现在当copy 死亡时,它将再次尝试从已释放的input_stream 释放内存。


    如果您不需要,我可以建议考虑不要将 ifstream 保留为字段。如果您仅使用它来读取测试数据,则对其进行处理并检查结果。考虑在此方法/函数中打开文件。尽量缩短此类流/变量的生命周期:)

    【讨论】:

    • 感谢您的解释!我有很多 C 包袱,我真的需要学习更多现代 C++。但是,当我尝试使用唯一指针时,会出现编译器错误(隐式删除的复制构造函数)。编译器错误是指唯一指针。
    • @T'n'E 请使用最后一个解决方案。我认为移动语义将在引擎盖下使用。
    • 我选择听从您的建议,考虑“如果您不需要它,请不要将ifstream 保留为字段” 并创建一个专用的加载和释放函数对于我可以在测试中调用的序列。
    • @T'n'E 加载/释放函数听起来也不是什么好玩的东西 :( 你应该遵循RAII 成语,所以只需使用范围、ctors 和 dtors,你的生活就会变得容易得多:)
    • @T'n'E 如果您对我的解决方案(升级版)感到满意,请标记为correct answer Thx!
    【解决方案2】:

    这是一个与您的原始版本类似但不使用指针/新的版本。复制时,文件也被新对象打开,位置和状态设置为原始对象。

    #include <string>
    #include <fstream>
    #include <stdexcept>
    
    class TestSequence {
    public:
        TestSequence(const std::string& dir)
            : TestSequence(dir + "/Values1.csv", dir + "/Values2.csv",
                           dir + "/Values3.csv")
        {}
        TestSequence(const std::string& val1_file, const std::string& val2_file,
                     const std::string& val3_file)
            : val1_file_path(val1_file),
              val2_file_path(val2_file),
              val3_file_path(val3_file),
              mp_val1_file(val1_file_path),
              mp_val2_file(val2_file_path),
              mp_val3_file(val3_file_path)
        {
            if(!(mp_val1_file && mp_val2_file && mp_val3_file))
                throw std::runtime_error("angst");
        }
        TestSequence(const TestSequence& rhs) :
            TestSequence(rhs.val1_file_path, rhs.val2_file_path, rhs.val3_file_path)
        {
            mp_val1_file.seekg(rhs.mp_val1_file.rdbuf()->pubseekoff(0, std::ios_base::cur, std::ios_base::in));
            mp_val2_file.seekg(rhs.mp_val2_file.rdbuf()->pubseekoff(0, std::ios_base::cur, std::ios_base::in));
            mp_val3_file.seekg(rhs.mp_val3_file.rdbuf()->pubseekoff(0, std::ios_base::cur, std::ios_base::in));
            mp_val1_file.setstate(rhs.mp_val1_file.rdstate());
            mp_val2_file.setstate(rhs.mp_val2_file.rdstate());
            mp_val3_file.setstate(rhs.mp_val3_file.rdstate());
        }
        TestSequence(TestSequence&&) = default;
        TestSequence& operator=(const TestSequence& rhs) {
            TestSequence tmp(rhs);
            std::swap(*this, tmp);
            return *this;
        }
        TestSequence& operator=(TestSequence&&) = default;
    
        virtual ~TestSequence() {}
    
    private:
        std::string val1_file_path;
        std::string val2_file_path;
        std::string val3_file_path;
    
        std::ifstream mp_val1_file;
        std::ifstream mp_val2_file;
        std::ifstream mp_val3_file;
    };
    

    【讨论】:

    • 这不会编译!因为std::ifstreamdoesn't have copy ctor。作者提到这个...
    • 老兄非常糟糕。移动语义和引用?!请不要使用那个。删除 copy-ctor 也无济于事......
    • 我认为 C++98 风格的 copy-only 类(如旧的auto_ptr)在这种情况下可以解决问题。我没有在 OP:s 示例中看到任何地方,除了移动之外的任何东西都是有意义的,但是好的,如果这些讨厌的伪装移动被移除,是否可以只使用正常的移动 ctor 和赋值运算符?跨度>
    • 好吧,我把它变成了更像副本的样子(如果支持的话),但我没有费心设置fmtflags
    猜你喜欢
    • 1970-01-01
    • 2013-09-04
    • 1970-01-01
    • 1970-01-01
    • 2018-12-25
    • 1970-01-01
    • 1970-01-01
    • 2016-01-30
    • 1970-01-01
    相关资源
    最近更新 更多