fix8  version 1.4.0
Open Source C++ FIX Framework
harness.cpp
Go to the documentation of this file.
1 //-----------------------------------------------------------------------------------------
2 /*
3 
4 Fix8 is released under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
5 
6 Fix8 Open Source FIX Engine.
7 Copyright (C) 2010-16 David L. Dight <fix@fix8.org>
8 
9 Fix8 is free software: you can redistribute it and / or modify it under the terms of the
10 GNU Lesser General Public License as published by the Free Software Foundation, either
11 version 3 of the License, or (at your option) any later version.
12 
13 Fix8 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14 even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 
16 You should have received a copy of the GNU Lesser General Public License along with Fix8.
17 If not, see <http://www.gnu.org/licenses/>.
18 
19 BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO
20 THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
21 COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY
22 KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO
24 THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
25 YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
26 
27 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT
28 HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED
29 ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
30 CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
31 NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR
32 THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH
33 HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
34 
35 */
36 
37 //-----------------------------------------------------------------------------------------
90 //-----------------------------------------------------------------------------------------
91 #include <iostream>
92 #include <memory>
93 #include <fstream>
94 #include <iomanip>
95 #include <sstream>
96 #include <vector>
97 #include <map>
98 #include <list>
99 #include <set>
100 #include <iterator>
101 #include <algorithm>
102 #include <typeinfo>
103 #ifdef _MSC_VER
104 #include <signal.h>
105 #else
106 #include <sys/ioctl.h>
107 #include <signal.h>
108 #include <termios.h>
109 #endif
110 
111 #include <errno.h>
112 #include <string.h>
113 
114 // f8 headers
115 #include <fix8/f8includes.hpp>
116 
117 #ifdef FIX8_HAVE_GETOPT_H
118 #include <getopt.h>
119 #endif
120 
121 #include <fix8/usage.hpp>
122 #include <fix8/consolemenu.hpp>
123 #include "Myfix_types.hpp"
124 #include "Myfix_router.hpp"
125 #include "Myfix_classes.hpp"
126 
127 #include "myfix.hpp"
128 
129 //-----------------------------------------------------------------------------------------
130 using namespace std;
131 using namespace FIX8;
132 
133 //-----------------------------------------------------------------------------------------
134 void print_usage();
135 const string GETARGLIST("hl:svqc:R:S:rp:L:");
136 bool term_received(false);
137 
138 //-----------------------------------------------------------------------------------------
140 {
141  { { 'c', "Create messages" }, &MyMenu::create_msgs },
142  { { 'e', "Edit messages" }, &MyMenu::edit_msgs },
143  { { 'd', "Delete one message" }, &MyMenu::delete_msg },
144  { { 'D', "Delete all messages" }, &MyMenu::delete_msgs },
145  { { 'p', "Print messages" }, &MyMenu::print_msgs },
146  { { 's', "Send messages" }, &MyMenu::send_msgs },
147  { { 'r', "Read messages from disk" }, &MyMenu::read_msgs },
148  { { 'S', "Send one message, optionally save before send" }, &MyMenu::send_msg },
149  { { 't', "Toggle heartbeat message display" }, &MyMenu::toggle_heartbeats },
150  { { '?', "Help" }, &MyMenu::help },
151  { { 'l', "Logout" }, &MyMenu::do_logout },
152  { { 'L', "Set Lines per page" }, &MyMenu::set_lpp },
153  { { 'v', "Show version info" }, &MyMenu::version_info },
154  { { 'x', "Exit" }, &MyMenu::do_exit },
155 };
156 
157 bool quiet(false);
158 
159 //-----------------------------------------------------------------------------------------
160 void sig_handler(int sig)
161 {
162  switch (sig)
163  {
164  case SIGTERM:
165  case SIGINT:
166 #ifndef _MSC_VER
167  case SIGQUIT:
168 #endif
169  term_received = true;
170  signal(sig, sig_handler);
171  break;
172  }
173 }
174 
175 //-----------------------------------------------------------------------------------------
176 int main(int argc, char **argv)
177 {
178  int val, lines(50);
179  bool server(false), reliable(false);
180  string clcf, replay_file;
181  unsigned next_send(0), next_receive(0);
182 
183  // for xterm or if tput is available, export LINES so we can use here
184  const char *gresult(getenv("LINES"));
185  if (gresult && *gresult)
186  {
187  const string result(gresult);
188  const int nlines(stoi(result));
189  if (nlines > 10)
190  lines = nlines - 4;
191  }
192 
193 #ifdef FIX8_HAVE_GETOPT_LONG
194  option long_options[]
195  {
196  { "help", 0, 0, 'h' },
197  { "version", 0, 0, 'v' },
198  { "log", 1, 0, 'l' },
199  { "config", 1, 0, 'c' },
200  { "replay", 1, 0, 'p' },
201  { "server", 0, 0, 's' },
202  { "send", 1, 0, 'S' },
203  { "receive", 1, 0, 'R' },
204  { "lines", 1, 0, 'L' },
205  { "quiet", 0, 0, 'q' },
206  { "reliable", 0, 0, 'r' },
207  { 0 },
208  };
209 
210  while ((val = getopt_long (argc, argv, GETARGLIST.c_str(), long_options, 0)) != -1)
211 #else
212  while ((val = getopt (argc, argv, GETARGLIST.c_str())) != -1)
213 #endif
214  {
215  switch (val)
216  {
217  case 'v':
218  cout << argv[0] << " for " FIX8_PACKAGE " version " FIX8_VERSION << endl;
219  cout << "Released under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3. See <http://fsf.org/> for details." << endl;
220  return 0;
221  case ':': case '?': return 1;
222  case 'h': print_usage(); return 0;
223  case 'l': GlobalLogger::set_global_filename(optarg); break;
224  case 'p': replay_file = optarg; break;
225  case 'c': clcf = optarg; break;
226  case 's': server = true; break;
227  case 'S': next_send = stoul(optarg); break;
228  case 'R': next_receive = stoul(optarg); break;
229  case 'L': lines = stoul(optarg); break;
230  case 'q': quiet = true; break;
231  case 'r': reliable = true; break;
232  default: break;
233  }
234  }
235 
236  RandDev::init();
237 
238  signal(SIGTERM, sig_handler);
239  signal(SIGINT, sig_handler);
240 #ifndef _MSC_VER
241  signal(SIGQUIT, sig_handler);
242 #endif
243 
244  try
245  {
246  const string conf_file(server ? clcf.empty() ? "myfix_server.xml" : clcf : clcf.empty() ? "myfix_client.xml" : clcf);
247 
248  if (server)
249  {
250  unique_ptr<ServerSessionBase> ms(new ServerSession<myfix_session_server>(TEX::ctx(), conf_file, "TEX1"));
251 
252  for (unsigned scnt(0); !term_received; )
253  {
254  if (!ms->poll())
255  continue;
256  unique_ptr<FIX8::SessionInstanceBase> inst(ms->create_server_instance());
257  if (!quiet)
258  inst->session_ptr()->control() |= Session::printnohb;
259  glout_info << "client(" << ++scnt << ") connection established.";
260  inst->start(true, next_send, next_receive);
261  cout << "Session(" << scnt << ") finished." << endl;
262  inst->stop();
263  }
264  }
265  else
266  {
267  unique_ptr<ClientSessionBase>
268  mc(reliable ? new ReliableClientSession<myfix_session_client>(TEX::ctx(), conf_file, "DLD1")
269  : new ClientSession<myfix_session_client>(TEX::ctx(), conf_file, "DLD1"));
270  if (!quiet)
271  mc->session_ptr()->control() |= Session::printnohb;
272 
273  if (!reliable)
274  {
275  const LoginParameters& lparam(mc->session_ptr()->get_login_parameters());
276  mc->start(false, next_send, next_receive, lparam._davi());
277  }
278  else
279  mc->start(false);
280 
281  ConsoleMenu cm(TEX::ctx(), cin, cout, lines);
282  MyMenu mymenu(*mc->session_ptr(), 0, cout, &cm);
283  mymenu.get_tty().set_raw_mode();
285 
286  // permit replaying of test message sets
287  if (!replay_file.empty() && mymenu.load_msgs(replay_file))
288  mymenu.send_lst();
289 
290  for(; !term_received;)
291  {
292  cout << endl;
293  if (mymenu.get_msg_cnt())
294  cout << '[' << mymenu.get_msg_cnt() << "] msgs; ";
295  cout << "?=help > " << flush;
296  char ch{};
297  mymenu.get_istr().get(ch);
298  cout << ch << endl;
299  if (mymenu.get_istr().bad() || ch == 0x3 || !mymenu.process(ch))
300  break;
301  }
302 
303  mymenu.get_tty().unset_raw_mode();
304  }
305  }
306  catch (f8Exception& e)
307  {
308  cerr << "exception: " << e.what() << endl;
309  }
310  catch (exception& e) // also catches Poco::Net::NetException
311  {
312  cerr << "exception: " << e.what() << endl;
313  }
314 
315  if (term_received)
316  cout << "terminated." << endl;
317  return 0;
318 }
319 
320 //-----------------------------------------------------------------------------------------
321 bool myfix_session_client::handle_application(const unsigned seqnum, const Message *&msg)
322 {
323  return enforce(seqnum, msg) || msg->process(_router);
324 }
325 
326 //-----------------------------------------------------------------------------------------
328 {
329  cout << get_session_state_string(before) << " => " << get_session_state_string(after) << endl;
330 }
331 
332 //-----------------------------------------------------------------------------------------
333 bool myfix_session_server::handle_application(const unsigned seqnum, const Message *&msg)
334 {
335  return enforce(seqnum, msg) || msg->process(_router);
336 }
337 
338 //-----------------------------------------------------------------------------------------
340 {
341  cout << get_session_state_string(before) << " => " << get_session_state_string(after) << endl;
342 }
343 
344 //-----------------------------------------------------------------------------------------
346 {
347  get_ostr() << endl;
348  get_ostr() << "Key\tCommand" << endl;
349  get_ostr() << "===\t=======" << endl;
350  for (const auto& pp : _handlers)
351  get_ostr() << pp.first._key << '\t' << pp.first._help << endl;
352  get_ostr() << endl;
353  return true;
354 }
355 
356 //-----------------------------------------------------------------------------------------
358 {
359  _ostr << "Enter number of lines per page (currently=" << _cm->get_lpp() << "): " << flush;
360  f8String str;
361  if (!_cm->GetString(_tty, str).empty())
362  _cm->set_lpp(stoi(str));
363  return true;
364 }
365 
366 //-----------------------------------------------------------------------------------------
368 {
369  if (!_session.is_shutdown())
370  {
371  _session.send(new TEX::Logout);
372  get_ostr() << "logout..." << endl;
373  }
375  return false; // will exit
376 }
377 
378 //-----------------------------------------------------------------------------------------
380 {
381  UsageMan um("harness", GETARGLIST, "");
382  um.setdesc("harness -- menu driven f8 test client/server");
383  um.add('s', "server", "run in server mode (default client mode)");
384  um.add('h', "help", "help, this screen");
385  um.add('v', "version", "print version then exit");
386  um.add('l', "log", "global log filename");
387  um.add('p', "replay", "name of fix input file to send on connect");
388  um.add('c', "config", "xml config (default: myfix_client.xml or myfix_server.xml)");
389  um.add('q', "quiet", "do not print fix output");
390  um.add('R', "receive", "set next expected receive sequence number");
391  um.add('L', "lines", "set number of screen lines in the console menu (default 50)");
392  um.add('S', "send", "set next send sequence number");
393  um.add('r', "reliable", "start in reliable mode");
394  um.print(cerr);
395 }
396 
397 //-----------------------------------------------------------------------------------------
399 {
400  return true;
401 }
402 
403 //-----------------------------------------------------------------------------------------
405 {
406  return true;
407 }
408 
409 //-----------------------------------------------------------------------------------------
411 {
412  _cm->CreateMsgs(_tty, _lst);
413  return true;
414 }
415 
416 //-----------------------------------------------------------------------------------------
418 {
419  _ostr << endl;
420  for (const auto& pp : package_info())
421  _ostr << pp.first << ": " << pp.second << endl;
422  return true;
423 }
424 
425 //-----------------------------------------------------------------------------------------
427 {
428  _cm->EditMsgs(_tty, _lst);
429  return true;
430 }
431 
432 //-----------------------------------------------------------------------------------------
434 {
435  _cm->DeleteMsgs(_tty, _lst);
436  return true;
437 }
438 
439 //-----------------------------------------------------------------------------------------
441 {
442  _cm->DeleteAllMsgs(_tty, _lst);
443  return true;
444 }
445 
446 //-----------------------------------------------------------------------------------------
448 {
449  for (auto *pp : _lst)
450  _session.send(pp);
451  _lst.clear();
452 }
453 
454 //-----------------------------------------------------------------------------------------
456 {
457  if (_lst.size() && _cm->get_yn("Send messages? (y/n):", true))
458  send_lst();
459  return true;
460 }
461 
462 //-----------------------------------------------------------------------------------------
464 {
465  for (const auto *pp : _lst)
466  _ostr << *pp << endl;
467  return true;
468 }
469 
470 //-----------------------------------------------------------------------------------------
472 {
473  unique_ptr<Message> msg(_cm->RemoveMsg(_tty, _lst));
474  if (msg.get())
475  {
476  string fname;
477  _ostr << endl;
478  bool save(_cm->get_yn("Save message after send? (y/n):", true));
479  _ostr << endl;
480  if (save)
481  {
482  _ostr << "Enter filename: " << flush;
483  _cm->GetString(_tty, fname);
484  }
485  if (_cm->get_yn("Send message? (y/n):", true))
486  {
487  _session.send(msg.get(), false);
488  if (save && !fname.empty())
489  save_msg(fname, msg.get());
490  }
491  }
492  return true;
493 }
494 
495 //-----------------------------------------------------------------------------------------
496 bool MyMenu::save_msg(const string& fname, Message *msg)
497 {
498 #if !defined FIX8_RAW_MSG_SUPPORT
499  cerr << endl << "RAW_MSG_SUPPORT support not enabled. Run configure with --enable-rawmsgsupport" << endl;
500 #else
501  if (exist(fname))
502  _ostr << endl << fname << " exists, will append message" << endl;
503  ofstream ofs(fname.c_str(), ios::app);
504  if (!ofs)
505  {
506  cerr << Str_error(errno, "Could not open file");
507  return false;
508  }
509  ofs << msg->get_rawmsg() << endl; // requires fix8 built with --enable-rawmsgsupport
510 #endif
511  return true;
512 }
513 
514 //-----------------------------------------------------------------------------------------
516 {
517  if (_session.control() & Session::printnohb)
518  {
519  _session.control().clear(Session::printnohb);
520  _session.control().set(Session::print);
521  get_ostr() << "Heartbeat display on";
522  }
523  else
524  {
525  _session.control().clear(Session::print);
526  _session.control().set(Session::printnohb);
527  get_ostr() << "Heartbeat display off";
528  }
529  return true;
530 }
531 
532 //-----------------------------------------------------------------------------------------
533 bool MyMenu::load_msgs(const string& fname)
534 {
535  ifstream ifs(fname.c_str());
536  if (!ifs)
537  {
538  cerr << Str_error(errno, "Could not open file");
539  return false;
540  }
541 
542  char buffer[FIX8_MAX_MSG_LENGTH];
543  unsigned loaded(0), skipped(0);
544  while (!ifs.eof())
545  {
546  ifs.getline(buffer, FIX8_MAX_MSG_LENGTH - 1);
547  if (!buffer[0])
548  continue;
549  Message *msg(Message::factory(TEX::ctx(), buffer));
550  if (msg->is_admin())
551  {
552  ++skipped;
553  continue;
554  }
555  sender_comp_id sci;
556  msg->Header()->get(sci);
557  target_comp_id tci;
558  msg->Header()->get(tci);
559  if (_session.get_sid().same_side_sender_comp_id(sci) && _session.get_sid().same_side_target_comp_id(tci))
560  {
561  ++loaded;
562  delete msg->Header()->remove(Common_SendingTime); // will re-add on send
563  msg->setup_reuse();
564  _lst.push_back(msg);
565  }
566  else
567  ++skipped;
568  }
569 
570  _ostr << loaded << " msgs loaded, " << skipped << " msgs skipped" << endl;
571 
572  return true;
573 }
574 
575 //-----------------------------------------------------------------------------------------
577 {
578  char cwd[FIX8_MAX_FLD_LENGTH];
579  _ostr << "Playback (*.playback) files in ";
580 #ifdef _MSC_VER
581  _ostr << _getcwd(cwd, sizeof(cwd)) << endl;
582  if (system("dir *.playback"));
583 #else
584  _ostr << getcwd(cwd, sizeof(cwd)) << endl;
585  if (system("ls -l *.playback"));
586 #endif
587  _ostr << endl;
588  _ostr << "Enter filename: " << flush;
589  string fname;
590  if (!_cm->GetString(_tty, fname).empty())
591  load_msgs(fname);
592  return true;
593 }
594 
static void init()
Initialise the random number generator.
Definition: hftest.hpp:205
bool do_logout()
Definition: harness.cpp:367
void print_usage()
Definition: harness.cpp:379
F8API std::string Str_error(const int err, const char *str=0)
Definition: f8utils.cpp:165
static const Handlers _handlers
Definition: hftest.hpp:171
FIX8::tty_save_state & get_tty()
Definition: hftest.hpp:197
virtual bool operator()(const FIX8::TEX::ExecutionReport *msg)
Definition: hftest.cpp:655
#define FIX8_MAX_FLD_LENGTH
Definition: f8config.h:571
bool quiet(false)
F8API BaseField * remove(const unsigned short fnum, Presence::const_iterator itr)
Definition: message.cpp:646
bool add(const char sw, const std::string &lsw, const std::string &help)
Definition: usage.hpp:73
ExecutionReport (8), application, 326 fields, 16 groups.
#define FIX8_PACKAGE
Definition: f8config.h:601
Client wrapper.
Logout (5), admin, 3 fields, 0 groups.
virtual bool is_admin() const
Definition: message.hpp:1151
bool delete_msgs()
Definition: harness.cpp:440
bool load_msgs(const std::string &fname)
Definition: harness.cpp:533
bool save_msg(const std::string &fname, FIX8::Message *msg)
Definition: harness.cpp:496
unsigned next_receive(0)
bool get(T &to) const
Definition: message.hpp:671
bool toggle_heartbeats()
Definition: harness.cpp:515
void state_change(const FIX8::States::SessionStates before, const FIX8::States::SessionStates after)
Definition: harness.cpp:327
bool print_msgs()
Definition: harness.cpp:463
unsigned next_send(0)
bool set_lpp()
Definition: harness.cpp:357
const F8MetaCntx & ctx()
Compiler generated metadata object, accessed through this function.
Base exception class.
Definition: f8exception.hpp:49
NewOrderSingle (D), application, 243 fields, 11 groups.
bool delete_msg()
Definition: harness.cpp:433
int main(int argc, char **argv)
Definition: harness.cpp:176
void setdesc(const std::string &desc)
Definition: usage.hpp:91
bool do_exit()
Definition: hftest.hpp:194
void print(std::ostream &os) const
Definition: usage.hpp:95
MessageBase * Header() const
Definition: message.hpp:1098
#define FIX8_MAX_MSG_LENGTH
Definition: f8config.h:576
bool handle_application(const unsigned seqnum, const FIX8::Message *&msg)
Definition: harness.cpp:321
F8API const Package_info & package_info()
Definition: f8utils.cpp:247
Console test harness menu.
Definition: consolemenu.hpp:48
const string GETARGLIST("hl:svqc:R:S:rp:L:")
bool version_info()
Definition: harness.cpp:417
std::map< const MenuItem, bool(MyMenu::*)(), MenuItem > Handlers
Definition: hftest.hpp:170
bool read_msgs()
Definition: harness.cpp:576
virtual bool operator()(const FIX8::TEX::NewOrderSingle *msg)
Definition: hftest.cpp:585
virtual bool process(Router &rt) const
Definition: message.hpp:1147
#define glout_info
Definition: logger.hpp:601
Simple menu system that will work with most term types.
Definition: hftest.hpp:151
void sig_handler(int sig)
Definition: harness.cpp:160
const unsigned short Common_SendingTime(52)
bool term_received(false)
A complete Fix message with header, body and trailer.
Definition: message.hpp:1058
void send_lst()
Definition: harness.cpp:447
void state_change(const FIX8::States::SessionStates before, const FIX8::States::SessionStates after)
Definition: harness.cpp:339
int hypersleep< h_seconds >(unsigned amt)
Definition: hypersleep.hpp:83
bool handle_application(const unsigned seqnum, const FIX8::Message *&msg)
Definition: harness.cpp:333
bool create_msgs()
Definition: harness.cpp:410
void setup_reuse()
Definition: message.hpp:1279
#define FIX8_VERSION
Definition: f8config.h:742
const char * what() const
Definition: f8exception.hpp:85
bool help()
Definition: harness.cpp:345
bool edit_msgs()
Definition: harness.cpp:426
bool exist(const std::string &fname)
Definition: f8utils.hpp:1068
bool send_msgs()
Definition: harness.cpp:455
bool send_msg()
Definition: harness.cpp:471
std::string f8String
Definition: f8types.hpp:47
Convenient program help/usage wrapper. Generates a standardised usage message.
Definition: usage.hpp:42
Reliable Client wrapper. This client attempts to recover from disconnects and login rejects...