1 /* $Id: test_ncbi_namedpipe.cpp 627740 2021-03-17 19:21:53Z ivanov $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Author: Vladimir Ivanov, Anton Lavrentiev
27 *
28 * File Description: Test program for CNamedPipe[Client|Server] classes
29 *
30 */
31
32 #include <ncbi_pch.hpp>
33 #include "../ncbi_priv.h"
34 #include <corelib/ncbiapp.hpp>
35 #include <corelib/ncbifile.hpp>
36 #include <corelib/ncbi_system.hpp>
37 #include <connect/ncbi_connutil.h>
38 #include <connect/ncbi_namedpipe.hpp>
39
40 #include "test_assert.h" // This header must go last
41
42 #define _STR(x) #x
43 #define STR(s) _STR(s)
44
45 #define DEFAULT_TIMEOUT 3
46
47
48 USING_NCBI_SCOPE;
49
50
51 // Test pipe name
52 #if defined(NCBI_OS_MSWIN)
53 const string kPipeName = "\\\\.\\pipe\\ncbi\\test_pipename";
54 #elif defined(NCBI_OS_UNIX)
55 const string kPipeName = "./.ncbi_test_pipename";
56 #endif
57
58 const size_t kNumSubBlobs = 10;
59 const size_t kSubBlobSize = 10*1024;
60 const size_t kBlobSize = kNumSubBlobs * kSubBlobSize;
61
62
63 ////////////////////////////////
64 // Auxiliary functions
65 //
66
67 // Reading from pipe
s_ReadPipe(CNamedPipe & pipe,void * buf,size_t buf_size,size_t msg_size,size_t * n_read)68 static EIO_Status s_ReadPipe(CNamedPipe& pipe, void* buf, size_t buf_size,
69 size_t msg_size, size_t* n_read)
70 {
71 size_t n_read_total = 0;
72 size_t x_read = 0;
73 EIO_Status status;
74
75 do {
76 status = pipe.Read((char*) buf + n_read_total,
77 buf_size - n_read_total, &x_read);
78 ERR_POST(Info <<
79 "Read from pipe: "
80 + NStr::UIntToString((unsigned int) x_read) + " byte(s): "
81 + string(IO_StatusStr(status)));
82 n_read_total += x_read;
83 } while (status == eIO_Success && n_read_total < msg_size);
84
85 *n_read = n_read_total;
86 return status;
87 }
88
89
90 // Writing to pipe
s_WritePipe(CNamedPipe & pipe,const void * buf,size_t size,size_t * n_written)91 static EIO_Status s_WritePipe(CNamedPipe& pipe, const void* buf, size_t size,
92 size_t* n_written)
93 {
94 size_t n_written_total = 0;
95 size_t x_written = 0;
96 EIO_Status status;
97
98 do {
99 status = pipe.Write((char*) buf + n_written_total,
100 size - n_written_total, &x_written);
101 ERR_POST(Info <<
102 "Write to pipe: "
103 + NStr::UIntToString((unsigned int) x_written) + " byte(s): "
104 + string(IO_StatusStr(status)));
105 n_written_total += x_written;
106 } while (status == eIO_Success && n_written_total < size);
107
108 *n_written = n_written_total;
109 return status;
110 }
111
112
113
114 ////////////////////////////////
115 // Test application
116 //
117
118 class CTest : public CNcbiApplication
119 {
120 public:
121 CTest(void);
122
123 virtual void Init(void);
124 virtual int Run (void);
125
126 protected:
127 int Client(int num);
128 int Server(void);
129
130 string m_PipeName;
131 STimeout m_Timeout;
132 };
133
134
CTest(void)135 CTest::CTest(void)
136 {
137 m_Timeout.sec = DEFAULT_TIMEOUT;
138 m_Timeout.usec = 0;
139 }
140
141
Init(void)142 void CTest::Init(void)
143 {
144 // Set error posting and tracing on maximum
145 //SetDiagTrace(eDT_Enable);
146 SetDiagPostLevel(eDiag_Info);
147 SetDiagPostAllFlags(SetDiagPostAllFlags(eDPF_Default)
148 | eDPF_All | eDPF_OmitInfoSev);
149 UnsetDiagPostFlag(eDPF_Line);
150 UnsetDiagPostFlag(eDPF_File);
151 UnsetDiagPostFlag(eDPF_Location);
152 UnsetDiagPostFlag(eDPF_LongFilename);
153 SetDiagTraceAllFlags(SetDiagPostAllFlags(eDPF_Default));
154
155 // Create command-line argument descriptions class
156 auto_ptr<CArgDescriptions> arg_desc(new CArgDescriptions);
157
158 // Specify USAGE context
159 arg_desc->SetUsageContext(GetArguments().GetProgramBasename(),
160 "Test named pipe API");
161
162 // Describe the expected command-line arguments
163 arg_desc->AddDefaultKey
164 ("basename", "basename",
165 "Base name for the pipe",
166 CArgDescriptions::eString, kPipeName);
167 arg_desc->AddDefaultKey
168 ("suffix", "suffix",
169 "Unique string that will be added to the base pipe name",
170 CArgDescriptions::eString, "");
171 arg_desc->AddPositional
172 ("mode", "Test mode",
173 CArgDescriptions::eString);
174 arg_desc->SetConstraint
175 ("mode", &(*new CArgAllow_Strings, "client", "server"));
176 arg_desc->AddOptionalPositional
177 ("timeout", "Input/output timeout"
178 " (default = " + string(STR(DEFAULT_TIMEOUT)) + ')',
179 CArgDescriptions::eDouble);
180 arg_desc->SetConstraint
181 ("timeout", new CArgAllow_Doubles(0.0, 200.0));
182
183 // Setup arg.descriptions for this application
184 SetupArgDescriptions(arg_desc.release());
185 }
186
187
Run(void)188 int CTest::Run(void)
189 {
190 const CArgs& args = GetArgs();
191
192 g_NCBI_ConnectRandomSeed
193 = (unsigned int) time(0) ^ NCBI_CONNECT_SRAND_ADDEND;
194 ::srand(g_NCBI_ConnectRandomSeed);
195
196 m_PipeName = args["basename"].AsString();
197 if ( m_PipeName.empty() ) {
198 m_PipeName = kPipeName;
199 }
200 if ( !args["suffix"].AsString().empty() ) {
201 m_PipeName += '_' + args["suffix"].AsString();
202 }
203 ERR_POST(Info << "Using pipe name: " + m_PipeName);
204 if (args["timeout"].HasValue()) {
205 double tv = args["timeout"].AsDouble();
206 m_Timeout.sec = (unsigned int) tv;
207 m_Timeout.usec = (unsigned int)((tv - m_Timeout.sec) * kMicroSecondsPerSecond);
208 }
209 if (args["mode"].AsString() == "client") {
210 SetDiagPostPrefix("Client");
211 for (int i = 1; i <= 3; ++i) {
212 int exitcode = Client(i);
213 if (exitcode)
214 return exitcode;
215 }
216 }
217 else if (args["mode"].AsString() == "server") {
218 SetDiagPostPrefix("Server");
219 return Server();
220 }
221 else {
222 _TROUBLE;
223 }
224 return 0;
225 }
226
227
228 /////////////////////////////////
229 // Named pipe client
230 //
231
Client(int num)232 int CTest::Client(int num)
233 {
234 int exitcode = 0;
235
236 if (::rand() & 1) {
237 SleepMilliSec(100);
238 }
239 ERR_POST(Info << "Starting client " + NStr::IntToString(num) + "...");
240
241 CNamedPipeClient pipe;
242 assert(pipe.IsClientSide());
243 assert(pipe.SetTimeout(eIO_Open, &m_Timeout) == eIO_Success);
244 assert(pipe.SetTimeout(eIO_Read, &m_Timeout) == eIO_Success);
245 assert(pipe.SetTimeout(eIO_Write, &m_Timeout) == eIO_Success);
246
247 EIO_Status status;
248 CDeadline timeout(g_STimeoutToCTimeout(&m_Timeout));
249 for (;;) {
250 // Wait for server to come up online
251 status = pipe.Open(m_PipeName, kDefaultTimeout, kSubBlobSize,
252 CNamedPipeClient::fNoLogIfClosed);
253 if (status == eIO_Success) {
254 break;
255 }
256 if (timeout.IsExpired()) {
257 status = eIO_Timeout;
258 }
259 if (status != eIO_Closed) {
260 ERR_POST(Error << "Open() failed: " << IO_StatusStr(status));
261 _TROUBLE;
262 }
263 ERR_POST(Info << "Waiting for server...");
264 SleepMilliSec(500);
265 }
266
267 char buf[kSubBlobSize];
268 size_t n_read = 0;
269 size_t n_written = 0;
270
271 if (num > 2 && (rand() & 1)) {
272 // Super quick write-and-flee behavior
273 ERR_POST(Info << "Quitting the server!");
274 assert(s_WritePipe(pipe, "Quit!", 5, &n_written) == eIO_Success);
275 assert(n_written == 5);
276 if (rand() & 1) {
277 status = pipe.Close();
278 if (status != eIO_Success) {
279 ERR_POST(Error << "Close() failed: " << IO_StatusStr(status));
280 _TROUBLE;
281 }
282 }
283 exitcode = 2;
284 goto out;
285 }
286
287 // "Hello" test
288 {{
289 assert(s_WritePipe(pipe, "Hello", 5, &n_written) == eIO_Success);
290 assert(n_written == 5);
291 assert(s_ReadPipe(pipe, buf, sizeof(buf), 2, &n_read) == eIO_Success);
292 assert(n_read == 2);
293 assert(::memcmp(buf, "OK", 2) == 0);
294 }}
295
296 // Big binary blob test
297 {{
298 // Send a very big binary blob
299 size_t i;
300 unsigned char* blob = (unsigned char*) ::malloc(kBlobSize + 1);
301 for (i = 0; i < kBlobSize; ++i) {
302 blob[i] = (unsigned char) i;
303 }
304 for (i = 0; i < kNumSubBlobs; ++i) {
305 assert(s_WritePipe(pipe, blob + i*kSubBlobSize, kSubBlobSize,
306 &n_written) == eIO_Success);
307 assert(n_written == kSubBlobSize);
308 }
309 // Receive the blob back
310 ::memset(blob, 0, kBlobSize);
311 assert(s_ReadPipe(pipe, blob, kBlobSize + 1, kBlobSize, &n_read)
312 == eIO_Success);
313 // Check its contents
314 for (i = 0; i < kBlobSize; ++i) {
315 assert(blob[i] == (unsigned char) i);
316 }
317 ::free(blob);
318 ERR_POST(Info << "Blob test is OK!");
319 }}
320 if (::rand() & 1) {
321 SleepMilliSec(100);
322 }
323 if (::rand() & 1) {
324 status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
325 if (status == eIO_Success) {
326 ERR_POST(Error << "Extra read succeeded");
327 _TROUBLE;
328 }
329 ERR_POST(Info << "Error expected, " << IO_StatusStr(status));
330 }
331 if (::rand() & 1) {
332 status = pipe.Close();
333 if (status != eIO_Success) {
334 ERR_POST(Error << "Close() failed: " << IO_StatusStr(status));
335 _TROUBLE;
336 }
337 status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
338 _ASSERT(status == eIO_Unknown);
339 }
340 out:
341 ERR_POST(Info << "TEST completed successfully");
342 return exitcode;
343 }
344
345
346 /////////////////////////////////
347 // Named pipe server
348 //
349
Server(void)350 int CTest::Server(void)
351 {
352 int exitcode = 1; // Normally getting killed at the test completion
353
354 ERR_POST(Info << "Starting server...");
355
356 char buf[kSubBlobSize];
357 size_t n_read = 0;
358 size_t n_written = 0;
359
360 CNamedPipeServer pipe(m_PipeName, &m_Timeout, kSubBlobSize + 512);
361
362 assert(pipe.IsServerSide());
363 assert(pipe.SetTimeout(eIO_Read, &m_Timeout) == eIO_Success);
364 assert(pipe.SetTimeout(eIO_Write, &m_Timeout) == eIO_Success);
365
366 EIO_Status status;
367
368 for (int n = 1; n <= 10; ++n) {
369 if (::rand() & 1) {
370 SleepMilliSec(100);
371 }
372 ERR_POST(Info << "Listening on \"" + m_PipeName + "\", round "
373 + NStr::IntToString(n) + "...");
374
375 status = pipe.Listen();
376 switch (status) {
377 case eIO_Success:
378 ERR_POST(Info << "Client connected!");
379
380 if (::rand() & 1) {
381 SleepMilliSec(100);
382 }
383 // "Hello" test
384 {{
385 assert(s_ReadPipe(pipe, buf, sizeof(buf), 5, &n_read) == eIO_Success);
386 assert(n_read == 5);
387 if (memcmp(buf, "Quit!", 5) == 0) {
388 ERR_POST(Info << "Quit received!");
389 exitcode = 0;
390 goto done;
391 }
392 assert(memcmp(buf, "Hello", 5) == 0);
393 assert(s_WritePipe(pipe, "OK", 2, &n_written) == eIO_Success);
394 assert(n_written == 2);
395 }}
396
397 if (::rand() & 1) {
398 SleepMilliSec(100);
399 }
400 // Big binary blob test
401 {{
402 // Receive a very big binary blob
403 size_t i;
404 unsigned char* blob = (unsigned char*) ::malloc(kBlobSize + 1);
405
406 assert(s_ReadPipe(pipe, blob, kBlobSize +1, kBlobSize, &n_read)
407 == eIO_Success);
408 assert(n_read == kBlobSize);
409
410 // Check its content
411 for (i = 0; i < kBlobSize; ++i) {
412 assert(blob[i] == (unsigned char) i);
413 }
414 // Write the blob back
415 for (i = 0; i < kNumSubBlobs; ++i) {
416 assert(s_WritePipe(pipe, blob + i*kSubBlobSize,
417 kSubBlobSize, &n_written)
418 == eIO_Success);
419 assert(n_written == kSubBlobSize);
420 }
421 ::memset(blob, 0, kBlobSize);
422 ::free(blob);
423 ERR_POST(Info << "Blob test is OK!");
424 }}
425 if (::rand() & 1) {
426 SleepMilliSec(100);
427 }
428 if (::rand() & 1) {
429 status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
430 if (status == eIO_Success) {
431 ERR_POST(Error << "Extra read succeeded");
432 _TROUBLE;
433 }
434 ERR_POST(Info << "Error expected, " << IO_StatusStr(status));
435 }
436 ERR_POST(Info << "Disconnecting client...");
437 status = pipe.Disconnect();
438 if (status != eIO_Success) {
439 ERR_POST(Error << "Disconnect() failed: " << IO_StatusStr(status));
440 _TROUBLE;
441 }
442 if (::rand() & 1) {
443 status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
444 _ASSERT(status == eIO_Unknown);
445 }
446 ERR_POST(Info << "Round completed successfully!");
447 break;
448
449 case eIO_Timeout:
450 ERR_POST(Info << "Timeout...");
451 break;
452
453 default:
454 ERR_POST(Error << IO_StatusStr(status));
455 _TROUBLE;
456 }
457 }
458
459 done:
460 // Close named pipe
461 status = pipe.Close();
462 assert(status == eIO_Success || status == eIO_Closed);
463 return exitcode;
464 }
465
466
467 ///////////////////////////////////
468 // APPLICATION OBJECT and MAIN
469 //
470
main(int argc,const char * argv[])471 int main(int argc, const char* argv[])
472 {
473 // Execute main application function
474 return CTest().AppMain(argc, argv);
475 }
476