在参数化 Google 测试中加载测试序列文件

Loading test sequence file in parameterised Google Test

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

// 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 上的分段错误)

从未达到测试。

我假设我误解了 Google 测试如何使用创建的参数,或者我应该如何处理 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)

你得到 SIGSEGV 因为你 "copy" 指针的值(例如 0x123123 地址)并且你得到 。因此,即使您设置为 nullptr 也无济于事,因为您的 TestSequence 的其他副本会记住旧地址。

如果你想避免这种情况,你应该覆盖 copy ctor and assign copy operator of your class. Best solution is to use 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= 和移动构造函数(也许它们会默认生成但不确定所以我更喜欢显式标记)。这取决于您的用例,您希望如何实现复制构造器和复制分配。在我的例子中,我重新打开缓冲区,但也许你还想复制缓冲区的位置或其他东西。只需创建新的 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 保留为字段。如果您仅使用它来读取测试数据,请对其进行处理并检查结果。考虑在此 method/function 中打开文件。尽量让 stream/variable 的生命尽可能短:)

这是一个与您的原始版本相似但未使用 pointers/new 的版本。复制时,文件也由新对象打开,位置和状态设置为原始对象。

#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;
};