zyre 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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