1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00
srs/trunk/src/utest/srs_utest_kernel2.cpp
stone 748aa8508f SRS5: Improve file writer performance by fwrite with cache. v5.0.133 (#3308)
* SrsFileWriter leverages libc buffer to boost dvr write speed.

* Refactor SrsFileWriter to use libc file functions mockable

* Add utest and refine code.

Co-authored-by: winlin <winlin@vip.126.com>

PICK 25eb21efe8
2023-01-08 12:06:38 +08:00

413 lines
17 KiB
C++

//
// Copyright (c) 2013-2023 The SRS Authors
//
// SPDX-License-Identifier: MIT or MulanPSL-2.0
//
#include <srs_utest_kernel2.hpp>
#include <srs_kernel_ps.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_buffer.hpp>
#include <srs_kernel_rtc_rtcp.hpp>
#include <srs_app_gb28181.hpp>
#include <srs_app_utility.hpp>
VOID TEST(KernelPSTest, PsPacketDecodeNormal)
{
srs_error_t err = srs_success;
MockPsHandler handler;
SrsPsContext context;
// Payload of GB28181 camera PS stream, the first packet:
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=0, Time=0
if (true) {
SrsRtpPacket rtp;
string raw = string(
"\x80\x60\x00\x00\x00\x00\x00\x00\x0b\xeb\xdf\xa1\x00\x00\x01\xba" \
"\x44\x68\x6e\x4c\x94\x01\x01\x30\x13\xfe\xff\xff\x00\x00\xa0\x05" \
"\x00\x00\x01\xbb\x00\x12\x80\x98\x09\x04\xe1\x7f\xe0\xe0\x80\xc0" \
"\xc0\x08\xbd\xe0\x80\xbf\xe0\x80\x00\x00\x01\xbc\x00\x5e\xfc\xff" \
"\x00\x24\x40\x0e\x48\x4b\x01\x00\x16\x9b\xa5\x22\x2e\xf7\x00\xff" \
"\xff\xff\x41\x12\x48\x4b\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" \
"\x0a\x0b\x0c\x0d\x0e\x0f\x00\x30\x1b\xe0\x00\x1c\x42\x0e\x07\x10" \
"\x10\xea\x02\x80\x01\xe0\x11\x30\x00\x00\x1c\x20\x2a\x0a\x7f\xff" \
"\x00\x00\x07\x08\x1f\xfe\x50\x3c\x0f\xc0\x00\x0c\x43\x0a\x00\x90" \
"\xfe\x02\xb1\x13\x01\xf4\x03\xff\xcb\x85\x54\xb9\x00\x00\x01\xe0" \
"\x00\x26\x8c\x80\x07\x21\x1a\x1b\x93\x25\xff\xfc\x00\x00\x00\x01" \
"\x67\x4d\x00\x1e\x9d\xa8\x28\x0f\x69\xb8\x08\x08\x0a\x00\x00\x03" \
"\x00\x02\x00\x00\x03\x00\x65\x08\x00\x00\x01\xe0\x00\x0e\x8c\x00" \
"\x03\xff\xff\xfc\x00\x00\x00\x01\x68\xee\x3c\x80\x00\x00\x01\xe0" \
"\x00\x0e\x8c\x00\x02\xff\xfc\x00\x00\x00\x01\x06\xe5\x01\xba\x80" \
"\x00\x00\x01\xe0\x35\x62\x8c\x00\x02\xff\xf8\x00\x00\x00\x01\x65", 256) + string(1156, 'x');
if (true) {
SrsBuffer b((char*)raw.data(), raw.length());
HELPER_ASSERT_SUCCESS(rtp.decode(&b));
}
SrsRtpRawPayload* rtp_raw = dynamic_cast<SrsRtpRawPayload*>(rtp.payload());
SrsBuffer b((char*)rtp_raw->payload, rtp_raw->nn_payload);
// There should be three video messages.
HELPER_ASSERT_SUCCESS(context.decode(&b, &handler));
EXPECT_EQ(3, handler.msgs_.size());
}
// We use the first packet bytes to mock the RTP packet seq 1 to 8.
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=1, Time=0
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=2, Time=0
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=3, Time=0
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=4, Time=0
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=5, Time=0
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=6, Time=0
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=7, Time=0
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=8, Time=0
for (int i = 0; i < 8; i++) {
SrsRtpPacket rtp;
string raw = string("\x80\x60\x00\x01\x00\x00\x00\x00\x0b\xeb\xdf\xa1", 12) + string(1400, 'x');
if (true) {
SrsBuffer b((char*)raw.data(), raw.length());
HELPER_ASSERT_SUCCESS(rtp.decode(&b));
}
SrsRtpRawPayload* rtp_raw = dynamic_cast<SrsRtpRawPayload*>(rtp.payload());
SrsBuffer b((char*)rtp_raw->payload, rtp_raw->nn_payload);
// Bytes continuity for the large video frame, got nothing message yet.
HELPER_ASSERT_SUCCESS(context.decode(&b, handler.clear()));
EXPECT_EQ(0, handler.msgs_.size());
}
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=9, Time=0
if (true) {
SrsRtpPacket rtp;
string raw = string("\x80\x60\x00\x09\x00\x00\x00\x00\x0b\xeb\xdf\xa1", 12) + string(1300, 'x')
+ string("\x00\x00\x01\xbd\x00\x6a\x8c\x80\x07\x21\x1a\x1b\x93\x25\xff\xf8" \
"\x00\x02\x00\x17\x00\x01\x80\x00\x00\xff\xa0\x05\xe0\xf1\xf0\x50" \
"\x18\x52\xd6\x5c\xa2\x78\x90\x23\xf9\xf6\x64\xba\xc7\x90\x5e\xd3" \
"\x80\x2f\x29\xad\x06\xee\x14\x62\xec\x6f\x77\xaa\x71\x80\xb3\x50" \
"\xb8\xd1\x85\x7f\x44\x30\x4f\x44\xfd\xcd\x21\xe6\x55\x36\x08\x6c" \
"\xb8\xd1\x85\x7f\x44\x30\x4f\x44\xfd\xcd\x21\xe6\x55\x36\x08\x6c" \
"\xc9\xf6\x5c\x74", 100);
if (true) {
SrsBuffer b((char*)raw.data(), raw.length());
HELPER_ASSERT_SUCCESS(rtp.decode(&b));
}
SrsRtpRawPayload* rtp_raw = dynamic_cast<SrsRtpRawPayload*>(rtp.payload());
SrsBuffer b((char*)rtp_raw->payload, rtp_raw->nn_payload);
// There should be one large video message, might be an I frame.
HELPER_ASSERT_SUCCESS(context.decode(&b, handler.clear()));
EXPECT_EQ(1, handler.msgs_.size());
}
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=10, Time=0
if (true) {
SrsRtpPacket rtp;
string raw("\x80\x60\x00\x0a\x00\x00\x00\x00\x0b\xeb\xdf\xa1" \
"\x57\xb3\xa3\xbc\x16\x2c\x3c\x9e\x69\x89\x48\xa4", 24);
if (true) {
SrsBuffer b((char*)raw.data(), raw.length());
HELPER_ASSERT_SUCCESS(rtp.decode(&b));
}
SrsRtpRawPayload* rtp_raw = dynamic_cast<SrsRtpRawPayload*>(rtp.payload());
SrsBuffer b((char*)rtp_raw->payload, rtp_raw->nn_payload);
// There should be a message of private stream, we ignore it, so we won't get it in callback.
HELPER_ASSERT_SUCCESS(context.decode(&b, handler.clear()));
EXPECT_EQ(0, handler.msgs_.size());
}
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=11, Time=3600
if (true) {
SrsRtpPacket rtp;
string raw = string("\x80\x60\x00\x0b\x00\x00\x0e\x10\x0b\xeb\xdf\xa1", 12)
+ string("\x00\x00\x01\xc0" \
"\x00\x82\x8c\x80\x09\x21\x1a\x1b\xa3\x51\xff\xff\xff\xf8\xff\xf9" \
"\x50\x40\x0e\xdf\xfc\x01\x2c\x2e\x84\x28\x23\x0a\x85\x82\xa2\x40" \
"\x90\x50\x2c\x14\x0b\x05\x42\x41\x30\x90\x44\x28\x16\x08\x84\x82", 52)
+ string(84, 'x');
if (true) {
SrsBuffer b((char*)raw.data(), raw.length());
HELPER_ASSERT_SUCCESS(rtp.decode(&b));
}
SrsRtpRawPayload* rtp_raw = dynamic_cast<SrsRtpRawPayload*>(rtp.payload());
SrsBuffer b((char*)rtp_raw->payload, rtp_raw->nn_payload);
// There should be one audio message.
HELPER_ASSERT_SUCCESS(context.decode(&b, handler.clear()));
EXPECT_EQ(1, handler.msgs_.size());
}
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=12, Time=3600
if (true) {
SrsRtpPacket rtp;
string raw = string("\x80\x60\x00\x0c\x00\x00\x0e\x10\x0b\xeb\xdf\xa1", 12)
+ string("\x00\x00\x01\xc0" \
"\x00\x8a\x8c\x80\x09\x21\x1a\x1b\xb3\x7d\xff\xff\xff\xf8\xff\xf9" \
"\x50\x40\x0f\xdf\xfc\x01\x2c\x2e\x88\x2a\x13\x0a\x09\x82\x41\x10" \
"\x90\x58\x26\x14\x13\x05\x02\xc2\x10\xa0\x58\x4a\x14\x0a\x85\x02", 52)
+ string(92, 'x');
if (true) {
SrsBuffer b((char*)raw.data(), raw.length());
HELPER_ASSERT_SUCCESS(rtp.decode(&b));
}
SrsRtpRawPayload* rtp_raw = dynamic_cast<SrsRtpRawPayload*>(rtp.payload());
SrsBuffer b((char*)rtp_raw->payload, rtp_raw->nn_payload);
// There should be another audio message.
HELPER_ASSERT_SUCCESS(context.decode(&b, handler.clear()));
EXPECT_EQ(1, handler.msgs_.size());
}
// PT=DynamicRTP-Type-96, SSRC=0xBEBDFA1, Seq=13, Time=3600
if (true) {
SrsRtpPacket rtp;
string raw = string("\x80\x60\x00\x0d\x00\x00\x0e\x10\x0b\xeb\xdf\xa1", 12)
+ string("\x00\x00\x01\xba" \
"\x44\x68\x6e\xbd\x14\x01\x01\x30\x13\xfe\xff\xff\x00\x00\xa0\x06" \
"\x00\x00\x01\xe0\x03\x4a\x8c\x80\x08\x21\x1a\x1b\xaf\x45\xff\xff" \
"\xf8\x00\x00\x00\x01\x61\xe0\x08\xbf\x3c\xb6\x63\x68\x4b\x7f\xea", 52)
+ string(816, 'x');
if (true) {
SrsBuffer b((char*)raw.data(), raw.length());
HELPER_ASSERT_SUCCESS(rtp.decode(&b));
}
SrsRtpRawPayload* rtp_raw = dynamic_cast<SrsRtpRawPayload*>(rtp.payload());
SrsBuffer b((char*)rtp_raw->payload, rtp_raw->nn_payload);
// There should be another audio message.
HELPER_ASSERT_SUCCESS(context.decode(&b, handler.clear()));
EXPECT_EQ(1, handler.msgs_.size());
}
}
VOID TEST(KernelPSTest, PsPacketHeaderClockDecode)
{
srs_error_t err = srs_success;
SrsPsContext context;
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x44\x68\x6e\x4c\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x00686c992, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x64\x68\x6e\x4c\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x10686c992, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\x68\x6e\x4c\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18686c992, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe8\x6e\x4c\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e86c992, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\x6e\x4c\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e96c992, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\x4c\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9ec992, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xcc\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9ed992, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdc\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edb92, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdd\x94\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edbb2, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdd\x9c\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edbb3, pkt.system_clock_reference_base_);
EXPECT_EQ(0, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdd\x9e\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edbb3, pkt.system_clock_reference_base_);
EXPECT_EQ(0x100, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdd\x9f\x01"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edbb3, pkt.system_clock_reference_base_);
EXPECT_EQ(0x180, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdd\x9f\x11"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edbb3, pkt.system_clock_reference_base_);
EXPECT_EQ(0x188, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdd\x9f\x19"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edbb3, pkt.system_clock_reference_base_);
EXPECT_EQ(0x18c, pkt.system_clock_reference_extension_);
}
if(true) {
SrsPsPacket pkt(&context);
SrsBuffer b((char*)"\x00\x00\x01\xba"/*start*/ "\x74\xe9\xee\xdd\x9f\x1b"/*clock*/ "\x01\x30\x13\xf8"/*mux*/, 14);
HELPER_ASSERT_SUCCESS(pkt.decode(&b));
EXPECT_EQ(0x18e9edbb3, pkt.system_clock_reference_base_);
EXPECT_EQ(0x18d, pkt.system_clock_reference_extension_);
}
}
VOID TEST(KernelLogTest, LogLevelString)
{
#ifdef SRS_LOG_LEVEL_V2
EXPECT_STREQ("FORB", srs_log_level_strings[SrsLogLevelForbidden]);
EXPECT_STREQ("TRACE", srs_log_level_strings[SrsLogLevelVerbose]);
EXPECT_STREQ("DEBUG", srs_log_level_strings[SrsLogLevelInfo]);
EXPECT_STREQ("INFO", srs_log_level_strings[SrsLogLevelTrace]);
EXPECT_STREQ("WARN", srs_log_level_strings[SrsLogLevelWarn]);
EXPECT_STREQ("ERROR", srs_log_level_strings[SrsLogLevelError]);
EXPECT_STREQ("OFF", srs_log_level_strings[SrsLogLevelDisabled]);
#else
EXPECT_STREQ("Forb", srs_log_level_strings[SrsLogLevelForbidden]);
EXPECT_STREQ("Verb", srs_log_level_strings[SrsLogLevelVerbose]);
EXPECT_STREQ("Debug", srs_log_level_strings[SrsLogLevelInfo]);
EXPECT_STREQ("Trace", srs_log_level_strings[SrsLogLevelTrace]);
EXPECT_STREQ("Warn", srs_log_level_strings[SrsLogLevelWarn]);
EXPECT_STREQ("Error", srs_log_level_strings[SrsLogLevelError]);
EXPECT_STREQ("Off", srs_log_level_strings[SrsLogLevelDisabled]);
#endif
}
VOID TEST(KernelLogTest, LogLevelStringV2)
{
EXPECT_EQ(srs_get_log_level("verbose"), SrsLogLevelVerbose);
EXPECT_EQ(srs_get_log_level("info"), SrsLogLevelInfo);
EXPECT_EQ(srs_get_log_level("trace"), SrsLogLevelTrace);
EXPECT_EQ(srs_get_log_level("warn"), SrsLogLevelWarn);
EXPECT_EQ(srs_get_log_level("error"), SrsLogLevelError);
EXPECT_EQ(srs_get_log_level("off"), SrsLogLevelDisabled);
EXPECT_EQ(srs_get_log_level_v2("trace"), SrsLogLevelVerbose);
EXPECT_EQ(srs_get_log_level_v2("debug"), SrsLogLevelInfo);
EXPECT_EQ(srs_get_log_level_v2("info"), SrsLogLevelTrace);
EXPECT_EQ(srs_get_log_level_v2("warn"), SrsLogLevelWarn);
EXPECT_EQ(srs_get_log_level_v2("error"), SrsLogLevelError);
EXPECT_EQ(srs_get_log_level_v2("off"), SrsLogLevelDisabled);
}
VOID TEST(KernelFileWriterTest, RealfileTest)
{
srs_error_t err;
string filename = "./test-realfile.log";
MockFileRemover disposer(filename);
if (true) {
SrsFileWriter f;
HELPER_EXPECT_SUCCESS(f.open(filename));
EXPECT_TRUE(f.is_open());
EXPECT_EQ(0, f.tellg());
HELPER_EXPECT_SUCCESS(f.write((void*) "HelloWorld", 10, NULL));
EXPECT_EQ(10, f.tellg());
f.seek2(5);
EXPECT_EQ(5, f.tellg());
HELPER_EXPECT_SUCCESS(f.write((void*) "HelloWorld", 10, NULL));
EXPECT_EQ(15, f.tellg());
off_t v = 0;
HELPER_EXPECT_SUCCESS(f.lseek(0, SEEK_CUR, &v));
EXPECT_EQ(15, v);
HELPER_EXPECT_SUCCESS(f.lseek(0, SEEK_SET, &v));
EXPECT_EQ(0, v);
HELPER_EXPECT_SUCCESS(f.lseek(10, SEEK_SET, &v));
EXPECT_EQ(10, v);
HELPER_EXPECT_SUCCESS(f.lseek(0, SEEK_END, &v));
EXPECT_EQ(15, v);
// There are 5 bytes empty lagging in file.
HELPER_EXPECT_SUCCESS(f.lseek(5, SEEK_END, &v));
EXPECT_EQ(20, v);
HELPER_EXPECT_SUCCESS(f.write((void*) "HelloWorld", 10, NULL));
EXPECT_EQ(30, f.tellg());
HELPER_EXPECT_SUCCESS(f.lseek(0, SEEK_SET, &v));
EXPECT_EQ(0, v);
HELPER_EXPECT_SUCCESS(f.write((void*) "HelloWorld", 10, NULL));
EXPECT_EQ(10, f.tellg());
}
SrsFileReader fr;
HELPER_ASSERT_SUCCESS(fr.open(filename));
// "HelloWorldWorld\0\0\0\0\0HelloWorld"
string str;
HELPER_ASSERT_SUCCESS(srs_ioutil_read_all(&fr, str));
EXPECT_STREQ("HelloWorldWorld", str.c_str());
EXPECT_STREQ("HelloWorld", str.substr(20).c_str());
}