506566bb75fc8ac0bfece10d017456b10d05ff1f
[feisty_meow.git] / octopi / library / tests_sockets / test_spocket.cpp
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
17 #include <application/hoople_main.h>
18 #include <basis/byte_array.h>
19 #include <basis/astring.h>
20 #include <loggers/file_logger.h>
21 #include <processes/launch_process.h>
22 #include <structures/static_memory_gremlin.h>
23 #include <sockets/internet_address.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
41 typedef abyte ip_address_holder[4];
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 LOG("OY POINT A");
104   ip_address_holder ip_address;  // accumulates the source address.
105   int rcv_port = 0;
106   bool is_client = false;
107   int send_size = 0;
108   int send_count = 0;
109
110   const char *DEFAULT_HOST = "127.0.0.1";
111   const int DEFAULT_PORT = 12348;
112   const int DEFAULT_SEND_SIZE = 1008;
113   const int DEFAULT_SEND_COUNT = 10;
114
115 LOG("OY POINT B");
116
117   if (_global_argc < 6) {
118     if (_global_argc > 1) {
119       LOG("\
120 This program takes five command line arguments to begin operation.\n\
121 These arguments (in order) are:\n\
122 \tIP address\t\tIn the form w.x.y.z\n\
123 \tPort number\t\tAs a short integer\n\
124 \tTester role\t\tEither \"client\" or \"server\"\n\
125 \tSend size\t\tThe size of the data to exchange.\n\
126 \tSend count\t\tThe number of \"packets\" to exchange.\n\
127 Note: it is expected that the client and server have equal send sizes;\n\
128 this allows the receiver to know when it's gotten all the data that's\n\
129 expected during a cycle.");
130       return 1;  // bail if they provided anything; otherwise we test.
131     } else {
132       parse_address(DEFAULT_HOST, ip_address);
133       rcv_port = DEFAULT_PORT;
134       is_client = false;  // we're a server to start with for unit test.
135       send_size = DEFAULT_SEND_SIZE;
136       send_count = DEFAULT_SEND_COUNT;
137     }
138   }
139
140 LOG("OY POINT C");
141
142   // only parse the parameters if we got enough from the user; otherwise we accept our
143   // defaults to do a simple test run.
144   if (_global_argc >= 6) {
145
146 LOG("OY POINT D");
147     if (!parse_address(_global_argv[1], ip_address)) {
148       LOG("failed to parse source address.");
149       return 9283;
150     }
151
152 LOG("OY POINT E");
153     LOG(a_sprintf("\tParsed a source of: \"%d.%d.%d.%d\".",
154         (int)ip_address[0], (int)ip_address[1], (int)ip_address[2],
155         (int)ip_address[3]));
156
157     // parse the next parameter: the port.
158     if (sscanf(_global_argv[2], "%d", &rcv_port) < 1) {
159       LOG("The port entry is malformed.  Exiting.");
160       return 3;
161     }
162     LOG(a_sprintf("\tGot a port of %d.", rcv_port));
163
164     // parse the next parameter: the role for this tester.
165     astring arg3 = _global_argv[3];
166     arg3.to_lower();
167     if (arg3 == astring("client")) is_client = true;
168     else if (arg3 == astring("server")) is_client = false;
169     else {
170       LOG("The tester role (client/server) is malformed.  Exiting.");
171       return 4;
172     }
173     if (is_client) LOG("\tTester role is \"client\".")
174     else LOG("\tTester role is \"server\".");
175
176     // parse the next parameter: the size of the sends.
177     if (sscanf(_global_argv[4], "%d", &send_size) < 1) {
178       LOG("The send size entry is malformed.  Exiting.");
179       return 5;
180     }
181     LOG(a_sprintf("\tGot a send size of %d.", send_size));
182
183     // parse the next parameter: the number of sends.
184     if (sscanf(_global_argv[5], "%d", &send_count) < 1) {
185       LOG("The send count entry is malformed.  Exiting.");
186       return 5;
187     }
188     LOG(a_sprintf("\tGot a send count of %d.", send_count));
189   }
190
191 LOG("OY POINT Q");
192
193   // package our parameters in a form the tester likes.
194   internet_address to_pass(byte_array(4, ip_address), "", rcv_port);
195
196   // now, construct our tester object.
197   spocket_tester *tester = new spocket_tester(to_pass);
198
199   // choose the appropriate action based on our role.
200   bool outcome;
201   if (is_client) {
202 LOG("client side is connecting");
203     outcome = tester->connect();
204   } else {
205 LOG("server side is accepting");
206     outcome = tester->accept(_global_argc != 1);
207   }
208   if (!outcome) {
209     const char *action = is_client? "connect" : "accept";
210     LOG(astring("Failed to ") + action + " on the tester.");
211     return 10;
212   }
213 LOG("success after conn/accept");
214
215 LOG("OY POINT T");
216
217   if (_global_argc == 1) {
218     // launch a paired duplicate of our test so we can chat.
219     launch_process zingit;
220     un_int kidnum;
221     un_int result = zingit.run(_global_argv[0],
222         astring(DEFAULT_HOST) + " " + a_sprintf("%d", DEFAULT_PORT) + " client "
223         + a_sprintf("%d", DEFAULT_SEND_SIZE) + " " + a_sprintf("%d", DEFAULT_SEND_COUNT),
224         launch_process::RETURN_IMMEDIATELY, kidnum);
225     ASSERT_EQUAL(result, 0, "launching paired process should start successfully");
226 LOG("OY POINT U.1");
227
228     // now we try again accepting from our client side.
229     outcome = tester->accept();
230 //hmmm: redundant below.
231 LOG("OY POINT U.2");
232     if (!outcome) {
233       const char *action = is_client? "connect" : "accept";
234 LOG("OY POINT U.3");
235       LOG(astring("Failed to ") + action + " on the tester.");
236       return 10;
237     }
238   }
239
240 LOG("OY POINT V");
241   // so, we're connected.  try sending the test packages out.
242   testing_statistics stats;  // to be filled by the tester.
243 LOG("OY POINT V.1");
244   outcome = tester->perform_test(send_size, send_count * 2, stats);
245     // multiply send_count since we count each side as one.
246 LOG("OY POINT V.2");
247   if (!outcome) {
248     LOG("Failed out of send_data; maybe other side terminated.");
249   }
250 LOG("OY POINT V.3");
251
252   stats.total_runs /= 2;  // cut down to the real number of tests.
253
254 LOG("OY POINT V.4");
255   if (!stats.total_runs)
256     stats.total_runs = 1;
257
258 LOG("OY POINT W");
259
260   // now report on the stats that we get from the data sending.
261   LOG(a_sprintf("Report for %d completed test cycles.", stats.total_runs));
262   LOG("");
263   LOG("\t\tsend stats\t\treceive stats");
264   LOG("\t\t----------\t\t-------------");
265   LOG(a_sprintf("bytes\t\t%d\t\t\t%d", stats.bytes_sent,
266       stats.bytes_received));
267   LOG(a_sprintf("time\t\t%d\t\t\t%d", stats.send_time, stats.receive_time));
268   LOG(a_sprintf("avg. bytes\t%d\t\t\t%d", stats.bytes_sent
269       / stats.total_runs / 2, stats.bytes_received / stats.total_runs / 2));
270   LOG("");
271   LOG(a_sprintf("round trip time: %d ms", stats.round_trip_time));
272 //hmmm: use the bandwidth measurer object!!!
273   double bandwidth = double(stats.bytes_sent + stats.bytes_received)
274       / stats.round_trip_time / 1024.0 * 1000.0;
275   LOG(a_sprintf("bandwidth overall: %f K/s", bandwidth));
276
277   if (_global_argc == 1) return final_report();
278   else return 0;  // no unit test report for non-top-level process
279 }
280
281 HOOPLE_MAIN(test_spocket, );
282