Test.pm (6312B)
1 package SL::Test::Server; 2 use strict; 3 4 use IPC::Open3; 5 6 sub new { 7 my $class = shift; 8 9 my $self = {}; 10 bless ($self, $class); 11 12 my $perl_args = ''; 13 if (defined $ARGV[0] && $ARGV[0] eq '-c') { 14 # Enable test coverage when -c is passed to the test 15 $perl_args = '-MDevel::Cover=-silent,1'; 16 } 17 18 $ENV{PATH} = "/bin:/usr/bin"; 19 delete @ENV{ 'IFS', 'CDPATH', 'ENV', 'BASH_ENV' }; 20 my $pid = open3(undef, undef, \*CHLD_ERR, "perl $perl_args -T sl -t -p 4729"); 21 22 $self->{pid} = $pid; 23 $self->{CHLD_ERR} = \*CHLD_ERR; 24 return $self; 25 } 26 27 sub readline { 28 my $self = shift; 29 return readline $self->{CHLD_ERR}; 30 } 31 32 sub DESTROY { 33 my $self = shift; 34 35 kill 'TERM', $self->{pid}; 36 waitpid( $self->{pid}, 0 ); 37 } 38 39 1; 40 41 package SL::Test::Client; 42 use strict; 43 use warnings; 44 45 use IO::Socket::SSL; 46 use JSON::XS; 47 use String::Random; 48 use Test; 49 use Time::HiRes qw(usleep); 50 51 require "msgs.pl"; 52 our (%msg_num, @msg_str); 53 54 sub new { 55 my $class = shift; 56 my $dont_register = shift || 0; 57 58 my $self = {}; 59 bless ($self, $class); 60 61 my $socket = undef; 62 while (! $socket) { 63 $socket = IO::Socket::SSL->new( 64 PeerHost => 'localhost', 65 PeerPort => 4729, 66 # certs are signed with absentmindedproductions.ca 67 SSL_verifycn_name => "absentmindedproductions.ca", 68 ) or usleep(100 * 1000); 69 } 70 die "failed connect or ssl handshake: $!,$SSL_ERROR" unless ($socket); 71 72 $self->{sock} = $socket; 73 $self->{device_id} = undef; 74 75 if ($dont_register == 0) { 76 my $string_gen = String::Random->new; 77 $self->{phnum} = '403' . $string_gen->randpattern('nnnnnnn'); 78 79 my $args = { phone_number => $self->{phnum}, os => 'unix' }; 80 $self->{device_id} = $self->device_add($args); 81 82 $self->device_update( "token_$self->{phnum}" ); 83 } 84 85 return $self; 86 } 87 88 sub device_add { 89 my ($self, $args, $status) = @_; 90 return $self->communicate('device_add', $status, $args); 91 } 92 93 sub device_update { 94 my ($self, $args, $status) = @_; 95 return $self->communicate('device_update', $status, $args); 96 } 97 98 sub list_add { 99 my ($self, $args, $status) = @_; 100 return $self->communicate('list_add', $status, $args); 101 } 102 103 sub list_update { 104 my ($self, $args, $status) = @_; 105 return $self->communicate('list_update', $status, $args); 106 } 107 108 sub list_join { 109 my ($self, $args, $status) = @_; 110 return $self->communicate('list_join', $status, $args); 111 } 112 113 sub list_leave { 114 my ($self, $args, $status) = @_; 115 return $self->communicate('list_leave', $status, $args); 116 } 117 118 sub friend_add { 119 my ($self, $args, $status) = @_; 120 return $self->communicate('friend_add', $status, $args); 121 } 122 123 sub friend_delete { 124 my ($self, $args, $status) = @_; 125 return $self->communicate('friend_delete', $status, $args); 126 } 127 128 sub lists_get { 129 my ($self, $status) = @_; 130 return $self->communicate('lists_get', $status); 131 } 132 133 sub lists_get_other { 134 my ($self, $status) = @_; 135 return $self->communicate('lists_get_other', $status); 136 } 137 138 sub communicate { 139 my ($self, $msg_type, $exp_status, $msg_data) = @_; 140 141 # If no expected status was passed in assume 'ok' 142 $exp_status = 'ok' if (! defined $exp_status); 143 144 # Only append "data" key if optional parameter $msg_data is defined 145 my $msg_args->{data} = $msg_data if (defined $msg_data); 146 147 # device_add is the only message type that does not require device_id as 148 # a mandatory argument 149 $msg_args->{device_id} = $self->{device_id} if ($msg_type ne 'device_add'); 150 151 $self->send_msg($msg_type, $msg_args); 152 my $resp = $self->recv_msg($msg_type); 153 154 # Check that the response status matches the expected status 155 ok( $resp->{status}, $exp_status ); 156 157 # Return the failure reason if the response status was error 158 return $resp->{reason} if ($resp->{status} eq 'err'); 159 160 # Everything looks good, return the response data 161 return $resp->{data}; 162 } 163 164 sub send_msg { 165 my ($self, $msg_type, $request) = @_; 166 167 # Request comes in as a hash ref, do this now to figure out length 168 my $payload = encode_json($request); 169 170 die "invalid message type $msg_type" unless (grep { $_ eq $msg_type } @msg_str); 171 172 my $version = 0; 173 my $payload_len = length($payload); 174 my $header = pack("nnn", $version, $msg_num{$msg_type}, $payload_len); 175 176 my $sent_bytes = 0; 177 $sent_bytes += $self->send_all($header, length($header)); 178 $sent_bytes += $self->send_all($payload, $payload_len); 179 180 return $sent_bytes; 181 } 182 183 sub send_all { 184 my ($self, $bytes, $bytes_total) = @_; 185 186 my $bytes_written = $self->{sock}->syswrite($bytes); 187 188 die "write failed: $!" if (!defined $bytes_written); 189 die "wrote $bytes_written instead of $bytes_total bytes" if ($bytes_written != $bytes_total); 190 191 return $bytes_total; 192 } 193 194 sub recv_msg { 195 my ($self, $exp_msg_type) = @_; 196 197 # Read header 198 my $header = $self->read_all(6); 199 my ($version, $msg_type, $payload_size) = unpack("nnn", $header); 200 201 # Check some things 202 die "unsupported protocol version $version" if ($version != 0); 203 die "unknown message type $msg_type" if ($msg_type >= @msg_str); 204 die "0 byte payload" if ($payload_size == 0); 205 die "unexpected message type $msg_type" if ($msg_num{$exp_msg_type} != $msg_type); 206 207 # Read again for payload, $payload_size > 0 208 my $payload = $self->read_all($payload_size); 209 210 # This will die if $payload is not JSON 211 my $response = decode_json($payload); 212 213 # Don't accept messages without an object root (ie array roots) 214 if (ref($response) ne "HASH") { 215 die "server didn't send back object root element"; 216 } 217 218 return $response; 219 } 220 221 sub read_all { 222 my ($self, $bytes_total) = @_; 223 224 my $data; 225 my $bytes_read = 0; 226 while ($bytes_total > 0) { 227 my $read = $self->{sock}->sysread($data, $bytes_total, $bytes_read); 228 229 die "read failed: $!" unless (defined $read); 230 die "read EOF on socket" if ($read == 0); 231 232 $bytes_total -= $read; 233 $bytes_read += $read; 234 } 235 236 return $data; 237 } 238 239 sub phnum { 240 my ($self) = @_; 241 return $self->{phnum}; 242 } 243 244 sub device_id { 245 my ($self) = @_; 246 return $self->{device_id}; 247 } 248 249 sub set_device_id { 250 my ($self, $new_id) = @_; 251 $self->{device_id} = $new_id; 252 } 253 254 sub msg_str { 255 return @msg_str; 256 } 257 258 1; 259 260 package SL::Test::Notify; 261 use strict; 262 263 use IPC::Open2; 264 265 sub new { 266 my $class = shift; 267 268 my $self = {}; 269 bless ($self, $class); 270 271 my $pid = open2(\*CHLD_OUT, undef, "perl", "SL/testd.pl"); 272 273 $self->{pid} = $pid; 274 $self->{chld_out} = \*CHLD_OUT; 275 return $self; 276 } 277 278 sub readline { 279 my $self = shift; 280 return readline $self->{chld_out}; 281 } 282 283 sub DESTROY { 284 my $self = shift; 285 286 kill 'TERM', $self->{pid}; 287 waitpid( $self->{pid}, 0 ); 288 } 289 290 1;