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

时间:2018-11-27 12:38:48

标签: c++ googletest

我正在尝试在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上的分段错误)

测试从未达到。

  • 我尝试在删除变量后将其设置为nullptr,并在删除变量之前对其进行检查,但是没有帮助。
  • 如果替换指向ifstream的指针,则会出现编译错误(错误:调用“ 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)

2 个答案:

答案 0 :(得分: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保留为字段。如果仅将其用于读取测试数据,请对其进行处理并检查结果。考虑使用此方法/功能打开文件。尝试使此类流/变量的寿命尽可能短:)

答案 1 :(得分:0)

这里的版本与您的原始版本相似,但未使用指针/新版本。复制后,文件也会由新对象打开,并且位置和状态也将设置为原始对象。

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