feisty meow concerns codebase  2.140
test_spocket.cpp
Go to the documentation of this file.
1 /*
2 * Name : test_spocket
3 * Author : Chris Koeritz
4 * Purpose: This is the "main" program for our sockets tester. It parses command
5 * line parameters and starts up the tester class.
6 **
7 * Copyright (c) 2001-$now By Author. This program is free software; you can *
8 * redistribute it and/or modify it under the terms of the GNU General Public *
9 * License as published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) any later version. This is online at: *
11 * http://www.fsf.org/copyleft/gpl.html *
12 * Please send any updates to: fred@gruntose.com *
13 */
14 
15 #include "spocket_tester.h"
16 
18 #include <basis/byte_array.h>
19 #include <basis/astring.h>
20 #include <loggers/file_logger.h>
24 #include <unit_test/unit_base.h>
25 
26 #include <stdio.h>
27 
28 using namespace application;
29 using namespace basis;
30 using namespace loggers;
31 using namespace mathematics;
32 using namespace processes;
33 using namespace sockets;
34 using namespace structures;
35 using namespace textual;
36 using namespace timely;
37 using namespace unit_test;
38 
39 #define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print))
40 
42 
43 class test_spocket : public virtual application_shell, public virtual unit_base
44 {
45 public:
46  test_spocket() {}
47  DEFINE_CLASS_NAME("test_spocket");
48  virtual int execute();
49 
50  bool parse_address(const astring &input, ip_address_holder &ip_address);
51 };
52 
53 //hmmm: get into a library; same func as in t_bcast_spocket
54 bool test_spocket::parse_address(const astring &input, ip_address_holder &ip_address)
55 {
56  int index = 0; // current storage position in our array.
57  int current_byte = 0;
58  const char *last_period = 0; // helps check for non-empty numbers.
59  bool got_digit = false;
60  for (const char *address = input.s(); *address; address++) {
61  if ( (*address <= '9') && (*address >= '0') ) {
62  current_byte *= 10; // shift over.
63  current_byte += *address - '0'; // add in current character.
64  got_digit = true;
65  } else if (*address == '.') {
66  got_digit = false;
67  if (last_period + 1 == address) {
68  LOG("The IP address entry has an empty digit. Exiting.");
69  return false;
70  }
71  last_period = address; // set our last period location.
72  if (current_byte > 255) {
73  LOG("The IP address entry has an illegal abyte. Exiting.");
74  return false;
75  }
76  ip_address[index] = abyte(current_byte); // store current byte.
77  current_byte = 0; // reset.
78  index++; // next place in array.
79  if (index > 3) break;
80  // stop if there are too many periods, but keep accumulated address.
81  } else {
82  LOG("The IP address entry has illegal characters. Exiting.");
83  return false;
84  }
85  }
86  // catch the case where we ran out of chars before adding the last byte.
87  if ( (index == 3) && got_digit) {
88  if (current_byte > 255) {
89  LOG("The IP address entry has an illegal abyte. Exiting.");
90  return false;
91  }
92  ip_address[index] = current_byte;
93  } else if (index < 4) {
94  LOG("The IP address entry is too short. Exiting.");
95  return false;
96  }
97  return true;
98 }
99 
100 int test_spocket::execute()
101 {
102  FUNCDEF("execute");
103  ip_address_holder ip_address; // accumulates the source address.
104  int rcv_port = 0;
105  bool is_client = false;
106  int send_size = 0;
107  int send_count = 0;
108 
109  const char *DEFAULT_HOST = "127.0.0.1";
110  const int DEFAULT_PORT = 12348;
111  const int DEFAULT_SEND_SIZE = 1008;
112  const int DEFAULT_SEND_COUNT = 10;
113 
114  if (_global_argc < 6) {
115  if (_global_argc > 1) {
116  LOG("\
117 This program takes five command line arguments to begin operation.\n\
118 These arguments (in order) are:\n\
119 \tIP address\t\tIn the form w.x.y.z\n\
120 \tPort number\t\tAs a short integer\n\
121 \tTester role\t\tEither \"client\" or \"server\"\n\
122 \tSend size\t\tThe size of the data to exchange.\n\
123 \tSend count\t\tThe number of \"packets\" to exchange.\n\
124 Note: it is expected that the client and server have equal send sizes;\n\
125 this allows the receiver to know when it's gotten all the data that's\n\
126 expected during a cycle.");
127  return 1; // bail if they provided anything; otherwise we test.
128  } else {
129  parse_address(DEFAULT_HOST, ip_address);
130  rcv_port = DEFAULT_PORT;
131  is_client = false; // we're a server to start with for unit test.
132  send_size = DEFAULT_SEND_SIZE;
133  send_count = DEFAULT_SEND_COUNT;
134  }
135  }
136 
137  // only parse the parameters if we got enough from the user; otherwise we accept our
138  // defaults to do a simple test run.
139  if (_global_argc >= 6) {
140 
141  if (!parse_address(_global_argv[1], ip_address)) {
142  LOG("failed to parse source address.");
143  return 9283;
144  }
145 
146  LOG(a_sprintf("\tParsed a source of: \"%d.%d.%d.%d\".",
147  (int)ip_address[0], (int)ip_address[1], (int)ip_address[2],
148  (int)ip_address[3]));
149 
150  // parse the next parameter: the port.
151  if (sscanf(_global_argv[2], "%d", &rcv_port) < 1) {
152  LOG("The port entry is malformed. Exiting.");
153  return 3;
154  }
155  LOG(a_sprintf("\tGot a port of %d.", rcv_port));
156 
157  // parse the next parameter: the role for this tester.
158  astring arg3 = _global_argv[3];
159  arg3.to_lower();
160  if (arg3 == astring("client")) is_client = true;
161  else if (arg3 == astring("server")) is_client = false;
162  else {
163  LOG("The tester role (client/server) is malformed. Exiting.");
164  return 4;
165  }
166  if (is_client) LOG("\tTester role is \"client\".")
167  else LOG("\tTester role is \"server\".");
168 
169  // parse the next parameter: the size of the sends.
170  if (sscanf(_global_argv[4], "%d", &send_size) < 1) {
171  LOG("The send size entry is malformed. Exiting.");
172  return 5;
173  }
174  LOG(a_sprintf("\tGot a send size of %d.", send_size));
175 
176  // parse the next parameter: the number of sends.
177  if (sscanf(_global_argv[5], "%d", &send_count) < 1) {
178  LOG("The send count entry is malformed. Exiting.");
179  return 5;
180  }
181  LOG(a_sprintf("\tGot a send count of %d.", send_count));
182  }
183 
184  // package our parameters in a form the tester likes.
185  internet_address to_pass(byte_array(4, ip_address), "", rcv_port);
186 
187  // now, construct our tester object.
188  spocket_tester *tester = new spocket_tester(to_pass);
189 
190  // choose the appropriate action based on our role.
191  bool outcome;
192  if (is_client) {
193  outcome = tester->connect();
194  } else {
195  outcome = tester->accept(_global_argc != 1);
196  }
197  if (!outcome) {
198  const char *action = is_client? "connect" : "accept";
199  LOG(astring("Failed to ") + action + " on the tester.");
200  return 10;
201  }
202 
203  if (_global_argc == 1) {
204  // launch a paired duplicate of our test so we can chat.
205  launch_process zingit;
206  un_int kidnum;
207  un_int result = zingit.run(_global_argv[0],
208  astring(DEFAULT_HOST) + " " + a_sprintf("%d", DEFAULT_PORT) + " client "
209  + a_sprintf("%d", DEFAULT_SEND_SIZE) + " " + a_sprintf("%d", DEFAULT_SEND_COUNT),
210  launch_process::RETURN_IMMEDIATELY, kidnum);
211  ASSERT_EQUAL(result, 0, "launching paired process should start successfully");
212 
213  // now we try again accepting from our client side.
214  outcome = tester->accept();
215 //hmmm: redundant below.
216  if (!outcome) {
217  const char *action = is_client? "connect" : "accept";
218  LOG(astring("Failed to ") + action + " on the tester.");
219  return 10;
220  }
221  }
222 
223  // so, we're connected. try sending the test packages out.
224  testing_statistics stats; // to be filled by the tester.
225  outcome = tester->perform_test(send_size, send_count * 2, stats);
226  // multiply send_count since we count each side as one.
227  if (!outcome) {
228  LOG("Failed out of send_data; maybe other side terminated.");
229  }
230 
231  stats.total_runs /= 2; // cut down to the real number of tests.
232 
233  if (!stats.total_runs)
234  stats.total_runs = 1;
235 
236  // now report on the stats that we get from the data sending.
237  LOG(a_sprintf("Report for %d completed test cycles.", stats.total_runs));
238  LOG("");
239  LOG("\t\tsend stats\t\treceive stats");
240  LOG("\t\t----------\t\t-------------");
241  LOG(a_sprintf("bytes\t\t%d\t\t\t%d", stats.bytes_sent,
242  stats.bytes_received));
243  LOG(a_sprintf("time\t\t%d\t\t\t%d", stats.send_time, stats.receive_time));
244  LOG(a_sprintf("avg. bytes\t%d\t\t\t%d", stats.bytes_sent
245  / stats.total_runs / 2, stats.bytes_received / stats.total_runs / 2));
246  LOG("");
247  LOG(a_sprintf("round trip time: %d ms", stats.round_trip_time));
248 //hmmm: use the bandwidth measurer object!!!
249  double bandwidth = double(stats.bytes_sent + stats.bytes_received)
250  / stats.round_trip_time / 1024.0 * 1000.0;
251  LOG(a_sprintf("bandwidth overall: %f K/s", bandwidth));
252 
253  if (_global_argc == 1) return final_report();
254  else return 0; // no unit test report for non-top-level process
255 }
256 
257 HOOPLE_MAIN(test_spocket, );
258 
The application_shell is a base object for console programs.
a_sprintf is a specialization of astring that provides printf style support.
Definition: astring.h:440
Provides a dynamically resizable ASCII character string.
Definition: astring.h:35
const char * s() const
synonym for observe. the 's' stands for "string", if that helps.
Definition: astring.h:113
void to_lower()
to_lower modifies "this" by replacing capitals with lower-case.
Definition: astring.cpp:528
A very common template for a dynamic array of bytes.
Definition: byte_array.h:36
Outcomes describe the state of completion for an operation.
Definition: outcome.h:31
Provides the capability to start processes in a variety of ways to run other applications.
static basis::un_int run(const basis::astring &app_name, const basis::astring &command_line, int flag, basis::un_int &child_id)
starts an application using the "app_name" as the executable to run.
this type of address describes a destination out on the internet.
bool perform_test(int size, int count, testing_statistics &stats_to_fill)
bool accept(bool wait=true)
#define DEFINE_CLASS_NAME(objname)
Defines the name of a class by providing a couple standard methods.
Definition: enhance_cpp.h:45
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition: enhance_cpp.h:57
Provides macros that implement the 'main' program of an application.
Implements an application lock to ensure only one is running at once.
char ** _global_argv
The guards collection helps in testing preconditions and reporting errors.
Definition: array.h:30
unsigned char abyte
A fairly important unit which is seldom defined...
Definition: definitions.h:51
unsigned int un_int
Abbreviated name for unsigned integers.
Definition: definitions.h:62
A logger that sends to the console screen using the standard output device.
An extension to floating point primitives providing approximate equality.
Definition: averager.h:21
Provides access to the operating system's socket methods.
Definition: base_address.h:26
A dynamic container class that holds any kind of object via pointers.
Definition: amorph.h:55
#include <time.h>
Definition: earth_time.cpp:37
Useful support functions for unit testing, especially within hoople.
Definition: unit_base.cpp:35
abyte ip_address_holder[4]
#define LOG(to_print)
HOOPLE_MAIN(test_spocket,)
#define ASSERT_EQUAL(a, b, test_name)
Definition: unit_base.h:38