zyre 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5946a8a32c3932017f3c6fc60d6a54e3a2196a5e193a769975a1c4b47cbb405e
4
- data.tar.gz: 5e51339b1fbc508dd0de7f3abc921aa7c2912cc243fda3a059d9057648c6acb9
3
+ metadata.gz: bc86fa098a17006c4e470186fb0f98910f0d443ef62351f18aa2c1f8962c9bcc
4
+ data.tar.gz: aac8d6fa401e07df184f74fa0f9ef4e31e46bb91997b5af9eafe71f8048460c8
5
5
  SHA512:
6
- metadata.gz: 5177153a30ea6f2f28ebfb7372e3055a51ec793ea5a5bd51c3afde8b41ba1909b88a5daae334ab5fd52db9858e10b1f0184e7a4f566f7f8ee7240311748e1ab4
7
- data.tar.gz: 61057878f14e360a57bf233ee5649dec9659686090d20f72461fcb955e387fa204e1afdcbadb8b5112e517a64547a27df5aa18eeea2706a9d65221724fbc89a5
6
+ metadata.gz: 6d44fcc7a7c5b63a6e471957d5361ea9595b34b2d35f458f61cb6ee5e19db8f624e5e857203fac49992dab6160937e57571069057894ec2d8e2c51801744e88f
7
+ data.tar.gz: d5be9dc4fd921a16679850d505bb8d81ad1bcd3029ad7b1ec84277957653c345f0ebdcedf3e7a9139111a7320fccdc797f1a316ff74c2860b3b56c098ffc546f
Binary file
data.tar.gz.sig CHANGED
Binary file
data/History.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Release History for zyre
2
2
 
3
3
  ---
4
+ ## v0.2.0 [2020-11-09] Michael Granger <ged@faeriemud.org>
5
+
6
+ Improvements:
7
+
8
+ - Add multipart messaging.
9
+ - Allow positional args for testing factory SHOUTs and WHISPERs
10
+
4
11
 
5
12
  ## v0.1.0 [2020-11-05] Michael Granger <ged@faeriemud.org>
6
13
 
data/README.md CHANGED
@@ -21,20 +21,84 @@ reliable group messaging over local area networks, an implementation of [the Zer
21
21
 
22
22
  ### Examples
23
23
 
24
- # Join the Zyre network on the network associated with the given interface
25
- # and dump events from the 'global' group to stderr.
24
+ Zyre is a P2P library which has two modes: pub/sub and direct messaging. To use it, you create a node, optionally join some groups (subscribing), and start it. Then you can send broadcast messages to all other nodes in a group, or direct messages to a particular node.
25
+
26
+ This example joins the Zyre network and dumps messages it sees in the 'global' group to stderr:
27
+
26
28
  node = Zyre::Node.new
27
- node.interface = 'igb0'
29
+ node.join( 'global' )
28
30
  node.start
29
31
 
30
- while event = node.recv
32
+ node.each_event do |event|
31
33
  event.print
32
34
  end
33
35
 
36
+ To send a direct message to a different node you need to know its `UUID`. There are number of ways to discover this:
37
+
38
+ # The UUIDs of all connected peers
39
+ node.peers
40
+ # The UUIDs of all the peers which have joined the `general` group
41
+ node.peers_by_group( 'general' )
42
+ # The UUID of the peer that sent the event
43
+ received_event.peer_uuid
44
+
45
+ You read events from the network with the Zyre::Node#recv method, but it blocks until an event arrives:
46
+
47
+ event = node.recv
48
+
49
+ You can also iterate over arriving events:
50
+
51
+ node.each do |event|
52
+ ...
53
+ end
54
+
55
+ or use Zyre::Node#each as an Enumerator:
56
+
57
+ five_events = node.each_event.take( 5 )
58
+
59
+ You can also wait for a certain type of event:
60
+
61
+ event = node.wait_for( :SHOUT )
62
+
63
+ and time out if it doesn't arrive within a certain length of time:
64
+
65
+ event = node.wait_for( :ENTER, timeout: 2.0 ) or
66
+ raise "No one else on the network!"
67
+
68
+ You can join a group with Zyre::Node#join:
69
+
70
+ node.join( 'alerts' )
71
+
72
+ and you can detect other nodes joining one of your groups by looking for JOIN events (Zyre::Event::Join objects):
73
+
74
+ node.wait_for( :JOIN )
75
+
76
+ You can publish a message to all nodes in a group with Zyre::Node#shout:
77
+
78
+ node.shout( "group1", "This is a message." )
79
+
80
+ and read it:
81
+
82
+ event = other_node.recv
83
+ event.is_multipart? # => false
84
+ event.msg # => "This is a message."
85
+ event.multipart_msg # => ["This is a message."]
86
+
87
+
88
+ Or publish a message with multiple parts:
89
+
90
+ node.shout( "group1", 'message.type', "This is a message." )
91
+
92
+ and read it:
93
+
94
+ event = other_node.recv
95
+ event.is_multipart? # => true
96
+ event.msg # => "message.type"
97
+ event.multipart_msg # => ["message.type", "This is a message."]
98
+
34
99
 
35
100
  ### To-Do
36
101
 
37
- * Implement Zyre::Node#peer_groups and Zyre::Node#peer_address
38
102
  * Implement the draft API methods on Zyre::Node
39
103
  * Hook up logging via `zsys_set_logsender`
40
104
  * Add richer matching to Zyre::Event#match.
@@ -121,6 +121,7 @@ rzyre_copy_string( VALUE string )
121
121
  const char *c_string = StringValueCStr( string );
122
122
  char *copy = (char *) zmalloc( strnlen(c_string, BUFSIZ) + 1 );
123
123
 
124
+ rzyre_log( "debug", "Copying string `%s`.", c_string );
124
125
  assert( copy );
125
126
  stpncpy( copy, c_string, strnlen(c_string, BUFSIZ) + 1 );
126
127
 
@@ -134,6 +135,7 @@ rzyre_copy_required_string( VALUE string, const char *field_name )
134
135
  if ( RB_TYPE_P(string, T_UNDEF) ) {
135
136
  rb_raise( rb_eArgError, "missing required field :%s", field_name );
136
137
  } else {
138
+ rzyre_log( "debug", "Required field %s is defined.", field_name );
137
139
  return rzyre_copy_string( string );
138
140
  }
139
141
  }
@@ -182,27 +184,37 @@ static VALUE
182
184
  rzyre_event_s_synthesize( int argc, VALUE *argv, VALUE klass )
183
185
  {
184
186
  VALUE rval, event_type, peer_uuid, kwargs, event_class;
185
- static VALUE kwvals[5];
187
+ VALUE kwvals[5] = { Qundef, Qundef, Qundef, Qundef, Qundef };
186
188
  static ID keyword_ids[5];
187
189
  zyre_event_t *ptr = NULL;
188
190
 
189
191
  // Parse the arguments + keyword arguments
190
192
  if ( !keyword_ids[0] ) {
191
- CONST_ID( keyword_ids[0], "peer_name");
192
- CONST_ID( keyword_ids[1], "headers");
193
- CONST_ID( keyword_ids[2], "peer_addr");
194
- CONST_ID( keyword_ids[3], "group");
195
- CONST_ID( keyword_ids[4], "msg");
193
+ CONST_ID( keyword_ids[0], "peer_name" );
194
+ CONST_ID( keyword_ids[1], "headers" );
195
+ CONST_ID( keyword_ids[2], "peer_addr" );
196
+ CONST_ID( keyword_ids[3], "group" );
197
+ CONST_ID( keyword_ids[4], "msg" );
196
198
  }
197
199
 
200
+ rzyre_log( "debug", "Scanning %d synthesize args.", argc );
198
201
  rb_scan_args( argc, argv, "2:", &event_type, &peer_uuid, &kwargs );
199
202
  if ( RTEST(kwargs) ) {
203
+ rzyre_log( "debug", " scanning keyword args: %s", RSTRING_PTR(rb_inspect(kwargs)) );
200
204
  rb_get_kwargs( kwargs, keyword_ids, 0, 5, kwvals );
201
205
  }
202
206
 
203
207
  // Translate the event type argument into the appropriate class and instantiate it
208
+ rzyre_log( "debug", "Creating an instance of a %s event.", RSTRING_PTR(rb_inspect(event_type )) );
204
209
  event_class = rb_funcall( klass, rb_intern("type_by_name"), 1, event_type );
205
- event_type = rb_funcall( event_class, rb_intern("type_name"), 0 );
210
+
211
+ if ( RTEST(event_class) ) {
212
+ event_type = rb_funcall( event_class, rb_intern("type_name"), 0 );
213
+ } else {
214
+ rb_raise( rb_eArgError, "don't know how to create %s events",
215
+ RSTRING_PTR(rb_inspect(event_type)) );
216
+ }
217
+
206
218
  rval = rb_class_new_instance( 0, NULL, event_class );
207
219
 
208
220
  // Set up the zyre_event memory for the object
@@ -213,6 +225,7 @@ rzyre_event_s_synthesize( int argc, VALUE *argv, VALUE klass )
213
225
  ptr->peer_uuid = rzyre_copy_string( peer_uuid );
214
226
 
215
227
  // Set the peer_name or default it if it wasn't specified
228
+ rzyre_log( "debug", "Starting type-specific setup." );
216
229
  if ( !RB_TYPE_P(kwvals[0], T_UNDEF) ) {
217
230
  ptr->peer_name = rzyre_copy_string( kwvals[0] );
218
231
  } else {
@@ -234,27 +247,25 @@ rzyre_event_s_synthesize( int argc, VALUE *argv, VALUE klass )
234
247
  ptr->group = rzyre_copy_required_string( kwvals[3], "group" );
235
248
  }
236
249
  else if ( streq(ptr->type, "WHISPER") ) {
237
- const char *msg_str = rzyre_copy_required_string( kwvals[4], "msg" );
238
- zmsg_t *msg = zmsg_new();
250
+ if ( RB_TYPE_P(kwvals[4], T_UNDEF) )
251
+ rb_raise( rb_eArgError, "missing required field :msg" );
239
252
 
240
- zmsg_addstr( msg, msg_str );
241
- ptr->msg = msg;
242
- msg = NULL;
253
+ rzyre_log( "debug", "Making a WHISPER zmsg from the :msg value: %s",
254
+ RSTRING_PTR(rb_inspect(kwvals[4])) );
255
+ ptr->msg = rzyre_make_zmsg_from( kwvals[4] );
243
256
  }
244
257
  else if ( streq(ptr->type, "SHOUT") ) {
245
- const char *msg_str = rzyre_copy_required_string( kwvals[4], "msg" );
246
- zmsg_t *msg = zmsg_new();
247
-
248
- zmsg_addstr( msg, msg_str );
258
+ if ( RB_TYPE_P(kwvals[4], T_UNDEF) )
259
+ rb_raise( rb_eArgError, "missing required field :msg" );
249
260
 
261
+ rzyre_log( "debug", "Making a SHOUT zmsg from the :msg value: %s",
262
+ RSTRING_PTR(rb_inspect(kwvals[4])) );
250
263
  ptr->group = rzyre_copy_required_string( kwvals[3], "group" );
251
- ptr->msg = msg;
252
- msg = NULL;
253
- }
254
- else if ( streq(ptr->type, "LEADER") ) {
255
- ptr->group = rzyre_copy_required_string( kwvals[3], "group" );
264
+ ptr->msg = rzyre_make_zmsg_from( kwvals[4] );
256
265
  }
257
266
 
267
+ rzyre_log_obj( rval, "debug", "Synthesized a %s event.", ptr->type );
268
+
258
269
  return rval;
259
270
  }
260
271
 
@@ -397,9 +408,32 @@ rzyre_event_group( VALUE self ) {
397
408
 
398
409
  /*
399
410
  * call-seq:
400
- * event.event_msg
411
+ * event.msg_size -> int
412
+ *
413
+ * Return the number of frames present in the event's message (if it has one). Returns
414
+ * nil if there is no message.
415
+ *
416
+ */
417
+ static VALUE
418
+ rzyre_event_msg_size( VALUE self )
419
+ {
420
+ zyre_event_t *ptr = rzyre_get_event( self );
421
+ zmsg_t *msg = zyre_event_msg( ptr );
422
+ VALUE rval = Qnil;
423
+
424
+ if ( msg ) {
425
+ rval = INT2FIX( zmsg_size(msg) );
426
+ }
427
+
428
+ return rval;
429
+ }
430
+
431
+
432
+ /*
433
+ * call-seq:
434
+ * event.msg
401
435
  *
402
- * Returns the incoming message payload.
436
+ * Returns the data from the first frame of the message from the receiver.
403
437
  */
404
438
  static VALUE
405
439
  rzyre_event_msg( VALUE self ) {
@@ -407,12 +441,11 @@ rzyre_event_msg( VALUE self ) {
407
441
  zmsg_t *msg = zyre_event_msg( ptr );
408
442
  VALUE rval = Qnil;
409
443
 
410
- // :TODO: Support multipart messages when Zyre does.
411
444
  if ( msg ) {
412
445
  zframe_t *frame = zmsg_first( msg );
413
446
  char *str = zframe_strdup( frame );
414
447
 
415
- rval = rb_utf8_str_new( str, zframe_size(frame) );
448
+ rval = rb_enc_str_new( str, zframe_size(frame), rb_ascii8bit_encoding() );
416
449
  rb_obj_freeze( rval );
417
450
 
418
451
  free( str );
@@ -422,6 +455,37 @@ rzyre_event_msg( VALUE self ) {
422
455
  }
423
456
 
424
457
 
458
+ /*
459
+ * call-seq:
460
+ * event.multipart_msg
461
+ *
462
+ * Returns the data from every frame of the message from the receiver.
463
+ */
464
+ static VALUE
465
+ rzyre_event_multipart_msg( VALUE self ) {
466
+ zyre_event_t *ptr = rzyre_get_event( self );
467
+ zmsg_t *msg = zyre_event_msg( ptr );
468
+ VALUE rval = rb_ary_new();
469
+
470
+ if ( msg ) {
471
+ zframe_t *frame = zmsg_first( msg );
472
+
473
+ while ( frame ) {
474
+ char *str = zframe_strdup( frame );
475
+ VALUE data = rb_enc_str_new( str, zframe_size(frame), rb_ascii8bit_encoding() );
476
+
477
+ rb_obj_freeze( data );
478
+ rb_ary_push( rval, data );
479
+
480
+ free( str );
481
+ frame = zmsg_next( msg );
482
+ }
483
+ }
484
+
485
+ return rval;
486
+ }
487
+
488
+
425
489
  /*
426
490
  * call-seq:
427
491
  * event.print
@@ -471,7 +535,9 @@ rzyre_init_event( void ) {
471
535
  rb_define_method( rzyre_cZyreEvent, "headers", rzyre_event_headers, 0 );
472
536
  rb_define_method( rzyre_cZyreEvent, "header", rzyre_event_header, 1 );
473
537
  rb_define_method( rzyre_cZyreEvent, "group", rzyre_event_group, 0 );
538
+ rb_define_method( rzyre_cZyreEvent, "msg_size", rzyre_event_msg_size, 0 );
474
539
  rb_define_method( rzyre_cZyreEvent, "msg", rzyre_event_msg, 0 );
540
+ rb_define_method( rzyre_cZyreEvent, "multipart_msg", rzyre_event_multipart_msg, 0 );
475
541
  rb_define_method( rzyre_cZyreEvent, "print", rzyre_event_print, 0 );
476
542
 
477
543
  rb_require( "zyre/event" );
@@ -526,23 +526,26 @@ rzyre_node_recv( VALUE self )
526
526
 
527
527
  /*
528
528
  * call-seq:
529
- * node.whisper( peer_uuid, message ) -> int
529
+ * node.whisper( peer_uuid, *messages ) -> int
530
530
  *
531
531
  * Send a +message+ to a single +peer+ specified as a UUID string.
532
532
  *
533
533
  */
534
534
  static VALUE
535
- rzyre_node_whisper( VALUE self, VALUE peer_uuid, VALUE msg )
535
+ rzyre_node_whisper( int argc, VALUE *argv, VALUE self )
536
536
  {
537
537
  zyre_t *ptr = rzyre_get_node( self );
538
- const char *peer_str = StringValueCStr( peer_uuid ),
539
- *msg_str = StringValueCStr( msg );
538
+ VALUE peer_uuid, msg_parts;
539
+ char *peer_uuid_str;
540
+ zmsg_t *msg;
540
541
  int rval;
541
542
 
542
- assert( peer_str );
543
- assert( msg_str );
543
+ rb_scan_args( argc, argv, "1*", &peer_uuid, &msg_parts );
544
544
 
545
- rval = zyre_whispers( ptr, peer_str, "%s", msg_str );
545
+ peer_uuid_str = StringValueCStr( peer_uuid );
546
+ msg = rzyre_make_zmsg_from( msg_parts );
547
+
548
+ rval = zyre_whisper( ptr, peer_uuid_str, &msg );
546
549
 
547
550
  return rval ? Qtrue : Qfalse;
548
551
  }
@@ -550,23 +553,26 @@ rzyre_node_whisper( VALUE self, VALUE peer_uuid, VALUE msg )
550
553
 
551
554
  /*
552
555
  * call-seq:
553
- * node.shout( group, message ) -> int
556
+ * node.shout( group, *messages ) -> int
554
557
  *
555
558
  * Send +message+ to a named +group+.
556
559
  *
557
560
  */
558
561
  static VALUE
559
- rzyre_node_shout( VALUE self, VALUE group, VALUE msg )
562
+ rzyre_node_shout( int argc, VALUE *argv, VALUE self )
560
563
  {
561
564
  zyre_t *ptr = rzyre_get_node( self );
562
- const char *group_str = StringValueCStr( group ),
563
- *msg_str = StringValueCStr( msg );
565
+ VALUE group, msg_parts;
566
+ char *group_str;
567
+ zmsg_t *msg;
564
568
  int rval;
565
569
 
566
- assert( group_str );
567
- assert( msg_str );
570
+ rb_scan_args( argc, argv, "1*", &group, &msg_parts );
571
+
572
+ group_str = StringValueCStr( group );
573
+ msg = rzyre_make_zmsg_from( msg_parts );
568
574
 
569
- rval = zyre_shouts( ptr, group_str, "%s", msg_str );
575
+ rval = zyre_shout( ptr, group_str, &msg );
570
576
 
571
577
  return rval ? Qtrue : Qfalse;
572
578
  }
@@ -819,8 +825,8 @@ rzyre_init_node( void )
819
825
 
820
826
  rb_define_method( rzyre_cZyreNode, "recv", rzyre_node_recv, 0 );
821
827
 
822
- rb_define_method( rzyre_cZyreNode, "whisper", rzyre_node_whisper, 2 );
823
- rb_define_method( rzyre_cZyreNode, "shout", rzyre_node_shout, 2 );
828
+ rb_define_method( rzyre_cZyreNode, "whisper", rzyre_node_whisper, -1 );
829
+ rb_define_method( rzyre_cZyreNode, "shout", rzyre_node_shout, -1 );
824
830
 
825
831
  rb_define_method( rzyre_cZyreNode, "peers", rzyre_node_peers, 0 );
826
832
  rb_define_method( rzyre_cZyreNode, "peers_by_group", rzyre_node_peers_by_group, 1 );
@@ -68,6 +68,64 @@ rzyre_log( const char *level, const char *fmt, va_dcl )
68
68
  }
69
69
 
70
70
 
71
+ /* --------------------------------------------------------------
72
+ * Utility functions
73
+ * -------------------------------------------------------------- */
74
+
75
+ // Struct for passing arguments through rb_protect to rzyre_add_frames_to_zmsg()
76
+ struct add_frames_to_zmsg_call {
77
+ zmsg_t *msg;
78
+ VALUE msg_parts;
79
+ };
80
+ typedef struct add_frames_to_zmsg_call add_frames_to_zmsg_call_t;
81
+
82
+
83
+ /*
84
+ * Add a frame for each object in an Array.
85
+ */
86
+ static VALUE
87
+ rzyre_add_frames_to_zmsg( VALUE call )
88
+ {
89
+ add_frames_to_zmsg_call_t *call_ptr = (add_frames_to_zmsg_call_t *)call;
90
+ VALUE msg_part;
91
+
92
+ for ( long i = 0 ; i < RARRAY_LEN(call_ptr->msg_parts) ; i++ ) {
93
+ msg_part = rb_ary_entry( call_ptr->msg_parts, i );
94
+ zmsg_addstr( call_ptr->msg, StringValueCStr(msg_part) );
95
+ }
96
+
97
+ return Qtrue;
98
+ }
99
+
100
+
101
+ /*
102
+ * Make and return a zmsg, with one frame per object in +messages+. Caller owns the returned
103
+ * zmsg. Can raise a TypeError if one of the +messages+ can't be stringified.
104
+ */
105
+ zmsg_t *
106
+ rzyre_make_zmsg_from( VALUE messages )
107
+ {
108
+ VALUE msgarray = rb_Array( messages );
109
+ zmsg_t *msg = zmsg_new();
110
+ static add_frames_to_zmsg_call_t call;
111
+ int state;
112
+
113
+ call.msg = msg;
114
+ call.msg_parts = msgarray;
115
+
116
+ rb_protect( rzyre_add_frames_to_zmsg, (VALUE)&call, &state );
117
+
118
+ if ( state ) {
119
+ zmsg_destroy( &msg );
120
+ rb_jump_tag( state );
121
+ }
122
+
123
+ return msg;
124
+ }
125
+
126
+
127
+
128
+
71
129
 
72
130
  /* --------------------------------------------------------------
73
131
  * Module methods
@@ -13,6 +13,7 @@
13
13
  #include <ruby.h>
14
14
  #include <ruby/intern.h>
15
15
  #include <ruby/thread.h>
16
+ #include <ruby/encoding.h>
16
17
 
17
18
  #include "zyre.h"
18
19
  #include "czmq.h"
@@ -83,6 +84,11 @@ extern VALUE rzyre_cZyrePoller;
83
84
  #define IsZyreEvent( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyreEvent )
84
85
  #define IsZyrePoller( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyrePoller )
85
86
 
87
+ /* --------------------------------------------------------------
88
+ * Utility functions
89
+ * -------------------------------------------------------------- */
90
+ extern zmsg_t * rzyre_make_zmsg_from _(( VALUE ));
91
+
86
92
 
87
93
  /* -------------------------------------------------------
88
94
  * Initializer functions
@@ -38,14 +38,14 @@ module Observability::Instrumentation::Zyre
38
38
  ###############
39
39
 
40
40
  ### Observer callback for the #whisper method.
41
- def observe_whisper( peer_uuid, msg )
42
- Observability.observer.add( peer_uuid: peer_uuid, message: msg )
41
+ def observe_whisper( peer_uuid, *msgs )
42
+ Observability.observer.add( peer_uuid: peer_uuid, messages: msgs )
43
43
  end
44
44
 
45
45
 
46
46
  ### Observer callback for the #shout method.
47
- def observe_shout( group, msg )
48
- Observability.observer.add( group: group, message: msg )
47
+ def observe_shout( group, *msgs )
48
+ Observability.observer.add( group: group, messages: msgs )
49
49
  end
50
50
 
51
51
  end # module Observability::Instrumentation::Zyre
@@ -12,7 +12,7 @@ module Zyre
12
12
 
13
13
 
14
14
  # Gem version (semver)
15
- VERSION = '0.1.0'
15
+ VERSION = '0.2.0'
16
16
 
17
17
 
18
18
  # Set up a logger for Zyre classes
@@ -60,6 +60,14 @@ class Zyre::Event
60
60
  end
61
61
 
62
62
 
63
+ ### Returns +true+ if the receiving event has a multipart message.
64
+ def multipart?
65
+ size = self.msg_size
66
+ return size && size > 1
67
+ end
68
+ alias_method :is_multipart?, :multipart?
69
+
70
+
63
71
  ### Return a string describing this event, suitable for debugging.
64
72
  def inspect
65
73
  details = self.inspect_details
@@ -154,8 +154,12 @@ module Zyre::Testing
154
154
 
155
155
 
156
156
  ### Generate a SHOUT event.
157
- def shout( **overrides )
157
+ def shout( group=nil, *msg, **overrides )
158
158
  uuid = overrides.delete( :peer_uuid ) || self.peer_uuid
159
+
160
+ overrides[:group] = group if group
161
+ overrides[:msg] = msg if !msg.empty?
162
+
159
163
  config = {
160
164
  peer_name: self.peer_name,
161
165
  group: self.group,
@@ -166,9 +170,14 @@ module Zyre::Testing
166
170
  end
167
171
 
168
172
 
169
- ### Generate a WHISPER event.
170
- def whisper( **overrides )
173
+ ### Generate a WHISPER event. The first positional argument, which would
174
+ ### normally be the UUID of the peer to WHISPER to is ignored, since the
175
+ ### generated event's +peer_uuid+ is the sending node's not the receiving one's.
176
+ def whisper( _ignored=nil, *msg, **overrides )
171
177
  uuid = overrides.delete( :peer_uuid ) || self.peer_uuid
178
+
179
+ overrides[:msg] = msg if !msg.empty?
180
+
172
181
  config = {
173
182
  peer_name: self.peer_name,
174
183
  msg: self.msg
@@ -31,7 +31,7 @@ RSpec.describe 'Observability::Instrumentation::Zyre', :observability do
31
31
  events = Observability.observer.sender.find_events( 'zyre.node.whisper' )
32
32
  expect( events.length ).to eq( 1 )
33
33
  expect( events.first[:peer_uuid] ).to eq( node2.uuid )
34
- expect( events.first[:message] ).to eq( 'a peer-to-peer message' )
34
+ expect( events.first[:messages] ).to eq( ['a peer-to-peer message'] )
35
35
 
36
36
  end
37
37
 
@@ -49,7 +49,7 @@ RSpec.describe 'Observability::Instrumentation::Zyre', :observability do
49
49
  events = Observability.observer.sender.find_events( 'zyre.node.shout' )
50
50
  expect( events.length ).to eq( 1 )
51
51
  expect( events.first[:group] ).to eq( 'observer-testing' )
52
- expect( events.first[:message] ).to eq( 'a peer-to-peer message' )
52
+ expect( events.first[:messages] ).to eq( ['a peer-to-peer message'] )
53
53
  end
54
54
 
55
55
  end
@@ -135,6 +135,55 @@ RSpec.describe( Zyre::Event ) do
135
135
  expect( result.peer_name ).to eq( 'S-' + peer_uuid[0, 6] )
136
136
  end
137
137
 
138
+
139
+ it "raises when creating a WHISPER with no msg" do
140
+ expect {
141
+ described_class.synthesize( :WHISPER, peer_uuid )
142
+ }.to raise_error( ArgumentError, /missing required field :msg/i )
143
+ end
144
+
145
+
146
+ it "raises when creating a SHOUT with no msg" do
147
+ expect {
148
+ described_class.synthesize( :SHOUT, peer_uuid, group: 'agroup' )
149
+ }.to raise_error( ArgumentError, /missing required field :msg/i )
150
+ end
151
+
152
+
153
+ it "raises when creating a SHOUT with no group" do
154
+ expect {
155
+ described_class.synthesize( :SHOUT, peer_uuid, msg: 'amsg' )
156
+ }.to raise_error( ArgumentError, /missing required field :group/i )
157
+ end
158
+
159
+
160
+ it "raises when creating an ENTER with no peer_addr" do
161
+ expect {
162
+ described_class.synthesize( :ENTER, peer_uuid )
163
+ }.to raise_error( ArgumentError, /missing required field :peer_addr/i )
164
+ end
165
+
166
+
167
+ it "raises when creating a JOIN with no group" do
168
+ expect {
169
+ described_class.synthesize( :JOIN, peer_uuid )
170
+ }.to raise_error( ArgumentError, /missing required field :group/i )
171
+ end
172
+
173
+
174
+ it "raises when creating a LEAVE with no group" do
175
+ expect {
176
+ described_class.synthesize( :LEAVE, peer_uuid )
177
+ }.to raise_error( ArgumentError, /missing required field :group/i )
178
+ end
179
+
180
+
181
+ it "raises when creating an unknown type of event" do
182
+ expect {
183
+ described_class.synthesize( :BACKUP, peer_uuid )
184
+ }.to raise_error( ArgumentError, /don't know how to create :BACKUP events/i )
185
+ end
186
+
138
187
  end
139
188
 
140
189
  end
@@ -162,13 +162,30 @@ RSpec.describe( Zyre::Node ) do
162
162
  node1.wait_for( :ENTER, peer_uuid: node2.uuid )
163
163
  node1.whisper( node2.uuid, TEST_WHISPER )
164
164
 
165
- ev = node2.recv
166
- expect( ev.type ).to eq( :ENTER )
165
+ ev = node2.wait_for( :WHISPER )
166
+ expect( ev ).to be_a( Zyre::Event::Whisper )
167
+ expect( ev ).to_not be_multipart
168
+ msg = ev.msg
169
+ expect( msg.encoding ).to eq( Encoding::ASCII_8BIT )
170
+ msg = msg.dup.force_encoding( 'utf-8' )
171
+ expect( msg ).to eq( TEST_WHISPER )
172
+ expect( ev.multipart_msg ).to eq( [TEST_WHISPER.b] )
173
+ end
167
174
 
168
- ev = node2.recv
169
- expect( ev.type ).to eq( :WHISPER )
170
- expect( ev.msg.encoding ).to eq( Encoding::UTF_8 )
171
- expect( ev.msg ).to eq( TEST_WHISPER )
175
+
176
+ it "can whisper a multipart message to another node" do
177
+ node1 = started_node()
178
+ node2 = started_node()
179
+
180
+ node1.wait_for( :ENTER, peer_uuid: node2.uuid )
181
+ node1.whisper( node2.uuid, 'poetry.snippet', TEST_WHISPER )
182
+
183
+ ev = node2.wait_for( :WHISPER, peer_uuid: node1.uuid )
184
+ expect( ev ).to be_a( Zyre::Event::Whisper )
185
+ expect( ev ).to be_multipart
186
+ expect( ev.msg.encoding ).to eq( Encoding::ASCII_8BIT )
187
+ expect( ev.msg ).to eq( 'poetry.snippet' )
188
+ expect( ev.multipart_msg ).to eq( ['poetry.snippet', TEST_WHISPER] )
172
189
  end
173
190
 
174
191
 
@@ -190,8 +207,42 @@ RSpec.describe( Zyre::Node ) do
190
207
 
191
208
  ev = node2.recv
192
209
  expect( ev ).to be_a( Zyre::Event::Shout )
193
- expect( ev.msg.encoding ).to eq( Encoding::UTF_8 )
194
- expect( ev.msg ).to eq( TEST_SHOUT )
210
+ expect( ev ).to_not be_multipart
211
+ expect( ev.msg.encoding ).to eq( Encoding::ASCII_8BIT )
212
+ expect( ev.msg ).to eq( TEST_SHOUT.b )
213
+ expect( ev.multipart_msg ).to eq( [TEST_SHOUT.b] )
214
+ end
215
+
216
+
217
+ it "can shout a multipart message to a group of nodes" do
218
+ node1 = started_node()
219
+ node2 = started_node()
220
+ node3 = started_node()
221
+
222
+ node1.join( 'ROOFTOP' )
223
+ node2.join( 'ROOFTOP' )
224
+ node3.join( 'ROOFTOP' )
225
+
226
+ 2.times do
227
+ node1.wait_for( :JOIN, group: 'ROOFTOP' )
228
+ end
229
+ node1.shout( 'ROOFTOP', "random.poetry", TEST_SHOUT )
230
+
231
+ ev = node2.wait_for( :SHOUT, group: 'ROOFTOP' )
232
+ expect( ev ).to be_a( Zyre::Event::Shout )
233
+ expect( ev ).to be_multipart
234
+ expect( ev.msg ).to eq( 'random.poetry'.b )
235
+ expect( ev.multipart_msg ).to eq( ['random.poetry'.b, TEST_SHOUT.b] )
236
+ end
237
+
238
+
239
+ it "handles unstringifiable messages gracefully" do
240
+ node1 = started_node()
241
+ node1.join( 'ROOFTOP' )
242
+
243
+ expect {
244
+ node1.shout( 'ROOFTOP', nil )
245
+ }.to raise_error( TypeError, /nil/i )
195
246
  end
196
247
 
197
248
 
@@ -236,6 +287,22 @@ RSpec.describe( Zyre::Node ) do
236
287
  end
237
288
 
238
289
 
290
+ it "knows what the headers of its peers are" do
291
+ node1 = started_node()
292
+ node2 = started_node() do |node|
293
+ node.headers = {
294
+ protocol_version: 2,
295
+ content_type: 'application/javascript',
296
+ start_time: Time.now.to_f
297
+ }
298
+ end
299
+
300
+ node1.wait_for( :enter, peer_uuid: node2.uuid )
301
+
302
+ expect( node1.peer_header_value(node2.uuid, 'Protocol-version') ).to eq( '2' )
303
+ end
304
+
305
+
239
306
  it "has a blocking iterator" do
240
307
  node = described_class.new
241
308
 
@@ -181,6 +181,20 @@ RSpec.describe( Zyre::Testing ) do
181
181
  end
182
182
 
183
183
 
184
+ it "can override SHOUT event message and group using positional parameters" do
185
+ event = factory.shout( 'control', 'data1', 'data2' )
186
+
187
+ expect( event ).to be_a( Zyre::Event::Shout )
188
+ expect( event.peer_uuid ).to eq( factory.peer_uuid )
189
+ expect( event.peer_name ).to eq( 'lancer-6' )
190
+ expect( event.headers ).to be_empty
191
+ expect( event.msg ).to eq( 'data1' )
192
+ expect( event ).to be_multipart
193
+ expect( event.multipart_msg ).to eq( ['data1'.b, 'data2'.b] )
194
+ expect( event.group ).to eq( 'control' )
195
+ end
196
+
197
+
184
198
  it "can create a valid WHISPER event" do
185
199
  event = factory.whisper
186
200
 
@@ -193,6 +207,14 @@ RSpec.describe( Zyre::Testing ) do
193
207
  end
194
208
 
195
209
 
210
+ it "can create a valid WHISPER event with a multipart msg" do
211
+ event = factory.whisper( msg: %w[three times fool] )
212
+
213
+ expect( event ).to be_multipart
214
+ expect( event.multipart_msg ).to eq( %w[three times fool] )
215
+ end
216
+
217
+
196
218
  it "can create a valid WHISPER event with overridden config" do
197
219
  uuid = SecureRandom.uuid.tr( '-', '' )
198
220
  event = factory.whisper( peer_uuid: uuid, msg: 'stop' )
@@ -206,6 +228,20 @@ RSpec.describe( Zyre::Testing ) do
206
228
  end
207
229
 
208
230
 
231
+ it "can override WHISPER event message using positional parameters" do
232
+ event = factory.whisper( 'ignored', 'data1', 'data2' )
233
+
234
+ expect( event ).to be_a( Zyre::Event::Whisper )
235
+ expect( event.peer_uuid ).to eq( factory.peer_uuid )
236
+ expect( event.peer_name ).to eq( 'lancer-6' )
237
+ expect( event.headers ).to be_empty
238
+ expect( event.msg ).to eq( 'data1' )
239
+ expect( event ).to be_multipart
240
+ expect( event.multipart_msg ).to eq( ['data1', 'data2'] )
241
+ expect( event.group ).to be_nil
242
+ end
243
+
244
+
209
245
  it "can create a valid LEAVE event" do
210
246
  event = factory.leave
211
247
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zyre
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Granger
@@ -33,7 +33,7 @@ cert_chain:
33
33
  83uuAYSy65yXDGXXPVBeKPVnYrqp91pqpS5Nh7wfuiCrE8lgU8PATh7K4BV1UhAT
34
34
  0MHbAT42wTYkfUj3
35
35
  -----END CERTIFICATE-----
36
- date: 2020-11-05 00:00:00.000000000 Z
36
+ date: 2020-11-09 00:00:00.000000000 Z
37
37
  dependencies:
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: loggability
metadata.gz.sig CHANGED
Binary file