File: test_PipeCapture.cc

package info (click to toggle)
gparted 1.6.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 20,752 kB
  • sloc: cpp: 34,868; sh: 5,073; makefile: 462; sed: 16; ansic: 9
file content (381 lines) | stat: -rw-r--r-- 13,200 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/* Copyright (C) 2017 Mike Fleetwood
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

/* Test PipeCapture
 *
 * All the tests work by creating a pipe(3) and using a separate thread to write data into
 * the pipe with PipeCapture running in the initial thread.  Captured data is then checked
 * that it either matches the input or different expected output depending on the features
 * being tested.
 */

#include "common.h"
#include "PipeCapture.h"
#include "gtest/gtest.h"

#include <stddef.h>
#include <stdio.h>
#include <sstream>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <sigc++/sigc++.h>
#include <glib.h>
#include <glibmm.h>

namespace GParted
{

// Repeat a C++ string count times, where count >= 0.
static std::string repeat( const std::string & str, size_t count )
{
	std::string result = "";
	while ( count -- > 0 )
		result += str;
	return result;
}


// Helper to construct and return message for equality assertion of C++ strings containing
// binary data used in:
//     EXPECT_BINARYSTRINGEQ( str1, str2 )
::testing::AssertionResult CompareHelperBinaryStringEQ( const char * lhs_expr, const char * rhs_expr,
                                                        const std::string & lhs, const std::string & rhs )
{
	// Loop comparing binary data in 16 byte amounts, stopping and reporting the first
	// difference encountered.
	bool diff = false;
	const char * p1 = lhs.data();
	const char * p2 = rhs.data();
	size_t len1 = lhs.length();
	size_t len2 = rhs.length();
	while ( len1 > 0 || len2 > 0 )
	{
		size_t cmp_span = BinaryStringChunkSize;
		cmp_span = ( len1 < cmp_span ) ? len1 : cmp_span;
		cmp_span = ( len2 < cmp_span ) ? len2 : cmp_span;
		if (cmp_span < BinaryStringChunkSize && len1 != len2)
		{
			diff = true;
			break;
		}
		if ( memcmp( p1, p2, cmp_span ) != 0 )
		{
			diff = true;
			break;
		}
		p1 += cmp_span;
		p2 += cmp_span;
		len1 -= cmp_span;
		len2 -= cmp_span;
	}

	if ( ! diff )
		return ::testing::AssertionSuccess();
	else
	{
		size_t offset = p1 - lhs.data();
		return ::testing::AssertionFailure()
		       << "      Expected: " << lhs_expr << "\n"
		       << "     Of length: " << lhs.length() << "\n"
		       << "To be equal to: " << rhs_expr << "\n"
		       << "     Of length: " << rhs.length() << "\n"
		       << "With first binary difference:\n"
		       << "< " << binary_string_to_print(offset, p1, len1) << "\n"
		       << "--\n"
		       << "> " << binary_string_to_print(offset, p2, len2);
	}
}

// Nonfatal assertion that binary data in C++ strings are equal.
#define EXPECT_BINARYSTRINGEQ(str1, str2)  \
	EXPECT_PRED_FORMAT2(CompareHelperBinaryStringEQ, str1, str2)

// Explicit test fixture class with common variables and methods used in each test.
// Reference:
//     Google Test, Primer, Test Fixtures: Using the Same Data Configuration for Multiple Tests
class PipeCaptureTest : public ::testing::Test
{
protected:
	PipeCaptureTest() : capturedstr( "text to be replaced" ),
	                    eof_signalled( false ), update_signalled( 0U )  {};

	virtual void SetUp();
	virtual void TearDown();

	static gboolean main_loop_quit( gpointer data );
	void writer_thread( const std::string & str );
	void run_writer_thread();

	static const size_t ReaderFD = 0;
	static const size_t WriterFD = 1;

	std::string inputstr;
	std::string expectedstr;
	Glib::ustring capturedstr;
	bool eof_signalled;
	unsigned update_signalled;
	int pipefds[2];
	Glib::RefPtr<Glib::MainLoop> glib_main_loop;

public:
	void eof_callback()  { eof_signalled = true; };
	void update_callback_leading_match();
};

// Further setup PipeCaptureTest fixture before running each test.  Create pipe and Glib
// main loop object.
void PipeCaptureTest::SetUp()
{
	ASSERT_TRUE( pipe( pipefds ) == 0 ) << "Failed to create pipe.  errno="
	                                    << errno << "," << strerror( errno );
	glib_main_loop = Glib::MainLoop::create();
}

// Tear down fixture after running each test.  Close reading end of the pipe.  Also
// re-closed the writing end of the pipe, just in case something went wrong in the test.
void PipeCaptureTest::TearDown()
{
	ASSERT_TRUE( close( pipefds[ReaderFD] ) == 0 ) << "Failed to close reading end of pipe.  errno="
	                                               << errno << "," << strerror( errno );
	close( pipefds[WriterFD] );
}

// Callback used to end the currently running Glib main loop.
gboolean PipeCaptureTest::main_loop_quit( gpointer data )
{
	static_cast<PipeCaptureTest *>( data )->glib_main_loop->quit();
	return false;  // One shot g_idle_add() callback
}

// Write the string into the pipe and close the pipe for writing.  Registers callback to
// end the currently running Glib main loop.
void PipeCaptureTest::writer_thread( const std::string & str )
{
	const size_t BlockSize = 4096;
	const char * writebuf = str.data();
	size_t remaining_size = str.length();
	while ( remaining_size > 0 )
	{
		size_t write_size = ( remaining_size > BlockSize ) ? BlockSize : remaining_size;
		ssize_t written = write( pipefds[WriterFD], writebuf, write_size );
		if ( written <= 0 )
		{
			ADD_FAILURE() << __func__ << "(): Failed to write to pipe.  errno="
			              << errno << "," << strerror( errno );
			break;
		}
		remaining_size -= written;
		writebuf += written;
	}
	ASSERT_TRUE( close( pipefds[WriterFD] ) == 0 ) << "Failed to close writing end of pipe.  errno="
	                                               << errno << "," << strerror( errno );
	g_idle_add( main_loop_quit, this );
}

// Create writer thread and run the Glib main loop.
void PipeCaptureTest::run_writer_thread()
{
	Glib::Thread::create( sigc::bind( sigc::mem_fun( *this, &PipeCaptureTest::writer_thread ),
	                                  inputstr ),
	                      false );
	glib_main_loop->run();
}

// Callback fired from CapturePipe counting calls and ensuring captured string matches
// leading portion of input string.
void PipeCaptureTest::update_callback_leading_match()
{
	update_signalled ++;
	EXPECT_BINARYSTRINGEQ( inputstr.substr( 0, capturedstr.raw().length() ),
	                       capturedstr.raw() );
	if ( HasFailure() )
		// No point trying to PipeCapture the rest of the input and report
		// hundreds of further failures in the same test, so end the currently
		// running Glib main loop immediately.
		// References:
		// *   Google Test, AdvancedGuide, Propagating Fatal Failures
		// *   Google Test, AdvancedGuide, Checking for Failures in the Current Test
		glib_main_loop->quit();
}

TEST_F( PipeCaptureTest, EmptyPipe )
{
	// Test capturing 0 bytes with no on EOF callback registered.
	inputstr = "";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.connect_signal();
	run_writer_thread();
	EXPECT_BINARYSTRINGEQ( inputstr, capturedstr.raw() );
	EXPECT_FALSE( eof_signalled );
}

TEST_F( PipeCaptureTest, EmptyPipeWithEOF )
{
	// Test capturing 0 bytes and registered on EOF callback occurs.
	inputstr = "";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.signal_eof.connect( sigc::mem_fun( *this, &PipeCaptureTest::eof_callback ) );
	pc.connect_signal();
	run_writer_thread();
	EXPECT_BINARYSTRINGEQ( inputstr, capturedstr.raw() );
	EXPECT_TRUE( eof_signalled );
}

TEST_F( PipeCaptureTest, ShortASCIIText )
{
	// Test capturing small amount of ASCII text.
	inputstr = "The quick brown fox jumps over the lazy dog";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.signal_eof.connect( sigc::mem_fun( *this, &PipeCaptureTest::eof_callback ) );
	pc.connect_signal();
	run_writer_thread();
	EXPECT_BINARYSTRINGEQ( inputstr, capturedstr.raw() );
	EXPECT_TRUE( eof_signalled );
}

TEST_F( PipeCaptureTest, LongASCIIText )
{
	// Test capturing 1 MiB of ASCII text (requiring multiple reads in PipeCapture).
	inputstr = repeat( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_\n", 16384 );
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.signal_eof.connect( sigc::mem_fun( *this, &PipeCaptureTest::eof_callback ) );
	pc.connect_signal();
	run_writer_thread();
	EXPECT_BINARYSTRINGEQ( inputstr, capturedstr.raw() );
	EXPECT_TRUE( eof_signalled );
}

TEST_F( PipeCaptureTest, LongASCIITextWithUpdate )
{
	// Test capturing 1 MiB of ASCII text, that registered update callback occurs and
	// intermediate captured string is a leading match for the input string.
	inputstr = repeat( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_\n", 16384 );
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.signal_eof.connect( sigc::mem_fun( *this, &PipeCaptureTest::eof_callback ) );
	pc.signal_update.connect( sigc::mem_fun( *this, &PipeCaptureTest::update_callback_leading_match ) );
	pc.connect_signal();
	run_writer_thread();
	EXPECT_BINARYSTRINGEQ( inputstr, capturedstr.raw() );
	EXPECT_GT( update_signalled, 0U );
	EXPECT_TRUE( eof_signalled );
}

TEST_F( PipeCaptureTest, MinimalBinaryCrash777973 )
{
	// Test for bug #777973.  Minimal test case of binary data returned by fsck.fat
	// as file names from a very corrupt FAT, leading to GParted crashing from a
	// segmentation fault.
	inputstr = "/LOST.DIR/!\xE2\x95\x9F\xE2\x88\xA9\xC2\xA0!\xE2\x95\x9F\xE2\x88\xA9\xC2";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.signal_eof.connect( sigc::mem_fun( *this, &PipeCaptureTest::eof_callback ) );
	pc.connect_signal();
	run_writer_thread();
	// Final \xC2 byte is part of an incomplete UTF-8 character so will be skipped by
	// PipeCapture.
	expectedstr = "/LOST.DIR/!\xE2\x95\x9F\xE2\x88\xA9\xC2\xA0!\xE2\x95\x9F\xE2\x88\xA9";
	EXPECT_BINARYSTRINGEQ( expectedstr, capturedstr.raw() );
	EXPECT_TRUE( eof_signalled );
}

TEST_F( PipeCaptureTest, ReadEmbeddedNULCharacter )
{
	// Test embedded NUL character in the middle of the input is read correctly.
	const char * buf = "ABC\0EF";
	inputstr = std::string( buf, 6 );
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.signal_eof.connect( sigc::mem_fun( *this, &PipeCaptureTest::eof_callback ) );
	pc.connect_signal();
	run_writer_thread();
	EXPECT_BINARYSTRINGEQ( inputstr, capturedstr.raw() );
	EXPECT_TRUE( eof_signalled );
}

TEST_F( PipeCaptureTest, ReadNULByteInMiddleOfMultiByteUTF8Character )
{
	// Test NUL byte in the middle of reading a multi-byte UTF-8 character.
	const char * buf = "\xC0\x00_45678";
	inputstr = std::string( buf, 8 );
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.signal_eof.connect( sigc::mem_fun( *this, &PipeCaptureTest::eof_callback ) );
	pc.connect_signal();
	run_writer_thread();
	// Initial \xC0 byte is part of an incomplete UTF-8 characters so will be skipped
	// by PipeCapture.
	buf = "\x00_45678";
	expectedstr = std::string( buf, 7 );
	EXPECT_BINARYSTRINGEQ( expectedstr, capturedstr.raw() );
	EXPECT_TRUE( eof_signalled );
}

TEST_F( PipeCaptureTest, LineDisciplineCarriageReturn )
{
	// Test PipeCapture line discipline processes carriage return character.
	inputstr = "1111\n2222\r33";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.connect_signal();
	run_writer_thread();
	expectedstr = "1111\n3322";
	EXPECT_BINARYSTRINGEQ( expectedstr, capturedstr.raw() );
}

TEST_F( PipeCaptureTest, LineDisciplineCarriageReturn2 )
{
	// Test PipeCapture line discipline processes multiple carriage return characters.
	inputstr = "1111\n2222\r33\r\r4";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.connect_signal();
	run_writer_thread();
	expectedstr = "1111\n4322";
	EXPECT_BINARYSTRINGEQ( expectedstr, capturedstr.raw() );
}

TEST_F( PipeCaptureTest, LineDisciplineBackspace )
{
	// Test PipeCapture line discipline processes backspace character.
	inputstr = "1111\n2222\b33";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.connect_signal();
	run_writer_thread();
	expectedstr = "1111\n22233";
	EXPECT_BINARYSTRINGEQ( expectedstr, capturedstr.raw() );
}

TEST_F( PipeCaptureTest, LineDisciplineBackspace2 )
{
	// Test PipeCapture line discipline processes too many backspace characters moving
	// the cursor back only to the beginning of the current line.
	inputstr = "1111\n2222\b\b\b\b\b\b33\b4";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.connect_signal();
	run_writer_thread();
	expectedstr = "1111\n3422";
	EXPECT_BINARYSTRINGEQ( expectedstr, capturedstr.raw() );
}

TEST_F( PipeCaptureTest, LineDisciplineSkipCtrlAB )
{
	// Test PipeCapture line discipline skips Ctrl-A and Ctrl-B.
	inputstr = "ij\x01kl\x02mn";
	PipeCapture pc( pipefds[ReaderFD], capturedstr );
	pc.connect_signal();
	run_writer_thread();
	expectedstr = "ijklmn";
	EXPECT_BINARYSTRINGEQ( expectedstr, capturedstr.raw() );
}

}  // namespace GParted