在参数化 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")));
我写了两个测试。我在测试序列的构造函数和析构函数以及每个测试的第一行添加了一个断点。结果如下:
- 为每个目录调用一次序列构造函数(预期)
- 为每个目录调用一次序列析构函数,顺序相反(意外)
- 在最后一个目录上再次调用序列析构函数(
delete
上的分段错误)
从未达到测试。
- 我曾尝试在删除变量后将变量设置为
nullptr
,并在删除前检查它,但没有帮助。
- 如果我替换指向
ifstream
的指针,我会得到编译错误(错误:调用 'TestSequence' 的隐式删除的复制构造函数)
我假设我误解了 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;
};
我正在尝试在 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")));
我写了两个测试。我在测试序列的构造函数和析构函数以及每个测试的第一行添加了一个断点。结果如下:
- 为每个目录调用一次序列构造函数(预期)
- 为每个目录调用一次序列析构函数,顺序相反(意外)
- 在最后一个目录上再次调用序列析构函数(
delete
上的分段错误)
从未达到测试。
- 我曾尝试在删除变量后将变量设置为
nullptr
,并在删除前检查它,但没有帮助。 - 如果我替换指向
ifstream
的指针,我会得到编译错误(错误:调用 'TestSequence' 的隐式删除的复制构造函数)
我假设我误解了 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;
};