lwes 0.1.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.
@@ -0,0 +1,116 @@
1
+ module LWES
2
+ class Struct
3
+
4
+ # creates a new Struct-based class, takes the following
5
+ # options hash:
6
+ #
7
+ # :db - pre-created LWES::TypeDB object
8
+ # this is required unless :file is given
9
+ # :file - pathname to the ESF file,
10
+ # this is required unless :db is given
11
+ # :class - Ruby base class name, if the ESF file only has one
12
+ # event defined (besides MetaEventInfo), then specifying
13
+ # it is optional, otherwise it is required when multiple
14
+ # events are defined in the same ESF :file given above
15
+ # :parent - parent class or module, the default is 'Object' putting
16
+ # the new class in the global namespace.
17
+ # :name - event name if it differs from the Ruby base class name
18
+ # given (or inferred) above. For DRY-ness, you are
19
+ # recommended to keep your event names and Ruby class
20
+ # names in sync and not need this option.
21
+ # :skip - Array of field names to skip from the Event defininition
22
+ # entirely, these could include fields that are only
23
+ # implemented by the Listener. This may also be a
24
+ # regular expression.
25
+ # :defaults - hash of default key -> value pairs to set at
26
+ # creation time
27
+ #
28
+ def self.new(options, &block)
29
+ db = options[:db]
30
+ db ||= begin
31
+ file = options[:file] or
32
+ raise ArgumentError, "TypeDB :db or ESF :file missing"
33
+ test ?r, file or
34
+ raise ArgumentError, "file #{file.inspect} not readable"
35
+ TypeDB.new(file)
36
+ end
37
+ dump = db.to_hash
38
+ klass = options[:class] || begin
39
+ # make it easier to deal with single event files
40
+ events = (dump.keys - [ :MetaEventInfo ])
41
+ events.size > 1 and
42
+ raise RuntimeError,
43
+ "multiple event defs available: #{events.inspect}\n" \
44
+ "pick one with :class"
45
+ events.first
46
+ end
47
+
48
+ name = options[:name] || klass
49
+ parent = options[:parent] || Object
50
+ event_def = dump[name.to_sym] or
51
+ raise RuntimeError, "#{name.inspect} not defined in #{file}"
52
+
53
+ # merge MetaEventInfo fields in
54
+ meta_event_info = dump[:MetaEventInfo]
55
+ alpha = proc { |a,b| a.first.to_s <=> b.first.to_s }
56
+ event_def = event_def.sort(&alpha)
57
+ if meta_event_info
58
+ seen = event_def.map { |(field, _)| field }
59
+ meta_event_info.sort(&alpha).each do |field_type|
60
+ seen.include?(field_type.first) or event_def << field_type
61
+ end
62
+ end
63
+
64
+ Array(options[:skip]).each do |x|
65
+ if Regexp === x
66
+ event_def.delete_if { |(f,_)| x =~ f.to_s }
67
+ else
68
+ if x.to_sym == :MetaEventInfo
69
+ meta_event_info.nil? and
70
+ raise RuntimeError, "MetaEventInfo not defined in #{file}"
71
+ meta_event_info.each do |(field,_)|
72
+ event_def.delete_if { |(f,_)| field == f }
73
+ end
74
+ else
75
+ event_def.delete_if { |(f,_)| f == x.to_sym }
76
+ end
77
+ end
78
+ end
79
+
80
+ tmp = ::Struct.new(*(event_def.map { |(field,_)| field }))
81
+ tmp = parent.const_set(klass, tmp)
82
+ tmp.const_set :TYPE_DB, db
83
+ tmp.const_set :NAME, name.to_s
84
+ ed = tmp.const_set :EVENT_DEF, {}
85
+ event_def.each { |(field,type)| ed[field] = type }
86
+ type_list = event_def.map { |(field,type)| [ field, field.to_s, type ] }
87
+ tmp.const_set :TYPE_LIST, type_list
88
+
89
+ defaults = options[:defaults] || {}
90
+ defaults = tmp.const_set :DEFAULTS, defaults.dup
91
+ tmp.class_eval(&block) if block_given?
92
+
93
+ # define a parent-level method, eval is faster than define_method
94
+ eval <<EOS
95
+ class ::#{tmp.name}
96
+ class << self
97
+ alias _new new
98
+ undef_method :new
99
+ def new(*args)
100
+ if Hash === (init = args.first)
101
+ rv = _new()
102
+ DEFAULTS.merge(init).each_pair { |k,v| rv[k] = v }
103
+ rv
104
+ else
105
+ rv = _new(*args)
106
+ DEFAULTS.each_pair { |k,v| rv[k] ||= v }
107
+ rv
108
+ end
109
+ end
110
+ end
111
+ end
112
+ EOS
113
+ tmp
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,24 @@
1
+ module LWES
2
+ class TypeDB
3
+
4
+ # create LWES::Struct-derived classes based on the contents
5
+ # of the TypeDB object. It is possible to place all classes
6
+ # into a namespace by specifying the :parent option to point
7
+ # to a class or module:
8
+ #
9
+ # module MyEvents; end
10
+ #
11
+ # type_db = LWES::TypeDB.new("my_events.esf")
12
+ # type_db.create_classes!(:parent => MyEvents)
13
+ #
14
+ # Assuming you had "Event1" and "Event2" defined in your "my_events.esf"
15
+ # file, then the classes MyEvents::Event1 and MyEvents::Event2 should
16
+ # now be accessible.
17
+ def create_classes!(options = {})
18
+ (to_hash.keys - [ :MetaEventInfo ]).map do |klass|
19
+ LWES::Struct.new({ :db => self, :class => klass }.merge(options))
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{lwes}
3
+ s.version = "0.1.0"
4
+ s.date = Time.now
5
+ s.authors = ["Erik S. Chang", "Frank Maritato"]
6
+ s.email = %q{lwes-devel@lists.sourceforge.net}
7
+ s.summary = %q{Ruby API for the Light Weight Event System}
8
+ s.homepage = %q{http://www.lwes.org/}
9
+ s.extensions = %w(ext/lwes/extconf.rb)
10
+ s.description = %q{
11
+ The LWES Light-Weight Event System is a framework for allowing the exchange of
12
+ information from many machines to many machines in a controlled, platform
13
+ neutral, language neutral way. The exchange of information is done in a
14
+ connectless fashion using multicast or unicast UDP, and using self describing
15
+ data so that any platform or language can translate it to it's local dialect.
16
+ }.strip
17
+ s.files = %w(
18
+ COPYING
19
+ ChangeLog
20
+ README
21
+ Rakefile
22
+ examples/demo.rb
23
+ examples/my_events.esf
24
+ ext/lwes/emitter.c
25
+ ext/lwes/extconf.rb
26
+ ext/lwes/lwes.c
27
+ ext/lwes/lwes_ruby.h
28
+ ext/lwes/numeric.c
29
+ ext/lwes/type_db.c
30
+ lib/lwes.rb
31
+ lib/lwes/emitter.rb
32
+ lib/lwes/struct.rb
33
+ lib/lwes/type_db.rb
34
+ lwes.gemspec
35
+ test/test_helper.rb
36
+ test/unit/test1.esf
37
+ test/unit/test2.esf
38
+ test/unit/test_emit_struct.rb
39
+ test/unit/test_emitter.rb
40
+ test/unit/test_struct.rb
41
+ test/unit/test_type_db.rb
42
+ )
43
+ end
@@ -0,0 +1,45 @@
1
+ require 'pp'
2
+ require 'tempfile'
3
+ require 'test/unit'
4
+ require 'lwes'
5
+
6
+ unless defined?(LISTENER_DEFAULTS)
7
+ BEFORE_DELAY = ENV['BEFORE_DELAY'] ? ENV['BEFORE_DELAY'].to_f : 0.5
8
+ AFTER_DELAY = ENV['AFTER_DELAY'] ? ENV['AFTER_DELAY'].to_f : 0.5
9
+ LISTENER_DEFAULTS = {
10
+ :address => ENV["LWES_TEST_ADDRESS"] || "127.0.0.1",
11
+ :iface => ENV["LWES_TEST_IFACE"] || "0.0.0.0",
12
+ :port => ENV["LWES_TEST_PORT"] ? ENV["LWES_TEST_PORT"].to_i : 12345,
13
+ :ttl => 60, # nil for no ttl)
14
+ }
15
+ end
16
+
17
+ def lwes_listener(&block)
18
+ cmd = "lwes-event-printing-listener" \
19
+ " -m #{@options[:address]}" \
20
+ " -i #{@options[:iface]}" \
21
+ " -p #{@options[:port]}"
22
+ out = Tempfile.new("out")
23
+ err = Tempfile.new("err")
24
+ $stdout.flush
25
+ $stderr.flush
26
+ pid = fork do
27
+ $stdout.reopen(out.path)
28
+ $stderr.reopen(err.path)
29
+ exec cmd
30
+ end
31
+ begin
32
+ # since everything executes asynchronously and our messaging,
33
+ # we need to ensure our listener is ready, then ensure our
34
+ # listener has printed something...
35
+ # XXX racy
36
+ sleep BEFORE_DELAY
37
+ yield
38
+ sleep AFTER_DELAY
39
+ ensure
40
+ Process.kill(:TERM, pid)
41
+ Process.waitpid2(pid)
42
+ assert_equal 0, err.size
43
+ end
44
+ out
45
+ end
@@ -0,0 +1,33 @@
1
+ # MetaEventInfo fields are allowed to be set in all other events, this
2
+ # can be though of as inheritance, in other words all other events in this
3
+ # file can have the fields defined in the MetaEventInfo
4
+ MetaEventInfo
5
+ {
6
+ ip_addr SenderIP; # sender_ip REQUIRED, CALCULATED
7
+ # ip address of the sender of the event
8
+ uint16 SenderPort; # sender_port REQUIRED, CALCULATED
9
+ # port of the sender of the event
10
+ int64 ReceiptTime; # time_received REQUIRED, CALCULATED
11
+ # time the event was received by the listener/journaller
12
+ uint16 SiteID; # receiver_id OPTIONAL, COMMAND_ARGS
13
+ # id of the listener/journaller
14
+ int16 enc; # encoding_type REQUIRED, FUNCTION_ARGS
15
+ # encoding type of strings in the event
16
+ string st; # sender_type OPTIONAL, FUNCTION_ARGS, 10
17
+ # sender type, this should be a short string which
18
+ # describes the sender type. (ie. "java", or "perl")
19
+ }
20
+
21
+ Event1
22
+ {
23
+ boolean t_bool; # test_bool OPTIONAL, CALCULATED
24
+ int16 t_int16; # test_int16 OPTIONAL, CALCULATED
25
+ uint16 t_uint16; # test_uint16 OPTIONAL, CALCULATED
26
+ int32 t_int32; # test_int32 OPTIONAL, CALCULATED
27
+ uint32 t_uint32; # test_uint32 OPTIONAL, CALCULATED
28
+ int64 t_int64; # test_int64 OPTIONAL, CALCULATED
29
+ uint64 t_uint64; # test_uint64 OPTIONAL, CALCULATED
30
+ ip_addr t_ip_addr; # test_ip_addr OPTIONAL, CALCULATED
31
+ string t_string; # test_string OPTIONAL, CALCULATED, 50
32
+ }
33
+
@@ -0,0 +1,14 @@
1
+ MetaEventInfo
2
+ {
3
+ ip_addr SenderIP;
4
+ }
5
+
6
+ EventBool
7
+ {
8
+ boolean B;
9
+ }
10
+
11
+ EventString
12
+ {
13
+ string S;
14
+ }
@@ -0,0 +1,101 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+
3
+ class TestEmitStruct < Test::Unit::TestCase
4
+
5
+ def setup
6
+ assert_kind_of Class, self.class.const_get(:Event1)
7
+ # assert self.class.const_get(:Event1).kind_of?(Struct)
8
+ @options = LISTENER_DEFAULTS.dup
9
+ end
10
+
11
+ def test_emit_struct_full
12
+ s = nil
13
+ out = lwes_listener do
14
+ assert_nothing_raised do
15
+ emitter = LWES::Emitter.new(@options)
16
+ s = Event1.new
17
+ s.t_bool = true
18
+ s.t_int16 = -1000
19
+ s.t_uint16 = 1000
20
+ s.t_int32 = -64444
21
+ s.t_uint32 = 64444
22
+ s.t_int64 = 10_000_000_000
23
+ s.t_uint64 = 10_000_000_000
24
+ s.t_ip_addr = '192.168.0.1'
25
+ s.t_string = "STRING"
26
+ emitter.emit(s)
27
+ end
28
+ end
29
+ out = out.readlines
30
+ s.members.each do |m|
31
+ value = s[m.to_sym] or next
32
+ regex = /\b#{m} = #{value};/
33
+ assert_equal 1, out.grep(regex).size,
34
+ "#{regex.inspect} didn't match #{out.inspect}"
35
+ end
36
+ end
37
+
38
+ def test_emit_from_class
39
+ opt = {
40
+ :t_bool => true,
41
+ :t_int16 => -1000,
42
+ :t_uint16 => 1000,
43
+ :t_int32 => -64444,
44
+ :t_uint32 => 64444,
45
+ :t_int64 => 10_000_000_000,
46
+ :t_uint64 => 10_000_000_000,
47
+ :t_ip_addr => '192.168.0.1',
48
+ :t_string => "STRING",
49
+ }
50
+ out = lwes_listener do
51
+ assert_nothing_raised do
52
+ emitter = LWES::Emitter.new(@options)
53
+ emitter.emit(Event1, opt)
54
+ end
55
+ end
56
+ out = out.readlines
57
+ opt.each do |m, value|
58
+ regex = /\b#{m} = #{value};/
59
+ assert_equal 1, out.grep(regex).size,
60
+ "#{regex.inspect} didn't match #{out.inspect}"
61
+ end
62
+ end
63
+
64
+ def test_emit_from_class_bad_type
65
+ out = lwes_listener do
66
+ e = assert_raises(TypeError) do
67
+ emitter = LWES::Emitter.new(@options)
68
+ opt = {
69
+ :t_int16 => -1000,
70
+ :t_uint16 => 1000,
71
+ :t_int32 => -64444,
72
+ :t_uint32 => 64444,
73
+ :t_int64 => 10_000_000_000,
74
+ :t_uint64 => 10_000_000_000,
75
+ :t_ip_addr => '192.168.0.1',
76
+ :t_string => true, #"STRING",
77
+ }
78
+ emitter.emit(Event1, opt)
79
+ end
80
+ end
81
+ assert out.readlines.empty?
82
+ end
83
+
84
+ def test_emit_alt_class_name
85
+ out = lwes_listener do
86
+ emitter = LWES::Emitter.new(@options)
87
+ emitter.emit(Event, :t_uint32 => 16384)
88
+ end
89
+ out = out.readlines
90
+ assert_match %r{^Event1\[\d+\]}, out.first
91
+ end
92
+
93
+ end
94
+
95
+ ESF_FILE = "#{File.dirname(__FILE__)}/test1.esf"
96
+ LWES::Struct.new(:file=>ESF_FILE,
97
+ :parent => TestEmitStruct)
98
+ LWES::Struct.new(:file=>ESF_FILE,
99
+ :parent => TestEmitStruct,
100
+ :name => "Event1",
101
+ :class => "Event")
@@ -0,0 +1,169 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+ require 'ipaddr'
3
+
4
+ class TestEmitter < Test::Unit::TestCase
5
+ def test_constants
6
+ assert_instance_of Module, LWES, "LWES is not a module"
7
+ assert_instance_of Class, LWES::Emitter, "LWES::Emitter is not a class"
8
+ end
9
+
10
+ def setup
11
+ @options = LISTENER_DEFAULTS.dup
12
+ end
13
+
14
+ def test_initialize
15
+ assert_instance_of LWES::Emitter, LWES::Emitter.new(@options)
16
+ end
17
+
18
+ def test_initialize_with_heartbeat
19
+ heartbeat = @options.merge(:heartbeat => 30)
20
+ assert_instance_of LWES::Emitter, LWES::Emitter.new(heartbeat)
21
+ end
22
+
23
+ def test_initialize_no_ttl
24
+ no_ttl = @options.dup
25
+ no_ttl.delete(:ttl)
26
+ assert_instance_of LWES::Emitter, LWES::Emitter.new(no_ttl)
27
+ end
28
+
29
+ def test_initialize_invalid
30
+ assert_raises(TypeError) {
31
+ LWES::Emitter.new(@options.merge(:address => nil))
32
+ }
33
+ end
34
+
35
+ def test_initialize_empty_options
36
+ assert_raises(TypeError) { LWES::Emitter.new({}) }
37
+ end
38
+
39
+ def test_emit_invalid
40
+ emitter = LWES::Emitter.new(@options)
41
+ assert_raises(TypeError) { emitter.emit "Invalid", nil }
42
+ assert_raises(ArgumentError) { emitter.emit nil, { :hello => "world" }}
43
+ end
44
+
45
+ def test_emit_empty_hash
46
+ emitter = LWES::Emitter.new(@options)
47
+ assert_nothing_raised { emitter.emit("Valid", Hash.new) }
48
+ end
49
+
50
+ def test_emit_non_empty_hashes
51
+ emitter = LWES::Emitter.new(@options)
52
+ out = lwes_listener do
53
+ assert_nothing_raised {
54
+ emitter.emit("ASDF", { :foo => "FOO", :nr => [ :int16, 50 ] })
55
+ }
56
+ end
57
+ lines = out.readlines
58
+ assert_match %r{\AASDF\b}, lines.first
59
+ assert ! lines.grep(/foo = FOO;/).empty?
60
+ assert ! lines.grep(/nr = 50;/).empty?
61
+ end
62
+
63
+ def test_emit_ip_addr_string
64
+ emitter = LWES::Emitter.new(@options)
65
+ event = { :string_ip => [ :ip_addr, "192.168.1.1" ] }
66
+ out = lwes_listener do
67
+ assert_nothing_raised { emitter.emit("STRING_IP", event) }
68
+ end
69
+ lines = out.readlines
70
+ assert_equal 1, lines.grep(/string_ip = 192.168.1.1/).size
71
+ end
72
+
73
+ def test_emit_ip_addr_int
74
+ emitter = LWES::Emitter.new(@options)
75
+ event = { :int_ip => [ :ip_addr, IPAddr.new("192.168.1.1").to_i ] }
76
+ out = lwes_listener do
77
+ assert_nothing_raised { emitter.emit("INT_IP", event) }
78
+ end
79
+ lines = out.readlines
80
+ assert_equal 1, lines.grep(/int_ip = 192.168.1.1/).size
81
+ end
82
+
83
+ def TODO_emit_ip_addr_object
84
+ emitter = LWES::Emitter.new(@options)
85
+ event = { :ip => IPAddr.new("192.168.1.1") }
86
+ out = lwes_listener do
87
+ assert_nothing_raised { emitter.emit("IP", event) }
88
+ end
89
+ lines = out.readlines
90
+ assert_equal 1, lines.grep(/\bip = 192.168.1.1/).size
91
+ end
92
+
93
+ def test_emit_invalid
94
+ emitter = LWES::Emitter.new(@options)
95
+ assert_raises(ArgumentError) { emitter.emit("JUNK", :junk => %r{junk}) }
96
+ end
97
+
98
+ def test_emit_booleans
99
+ emitter = LWES::Emitter.new(@options)
100
+ event = { :true => true, :false => false }
101
+ out = lwes_listener do
102
+ assert_nothing_raised { emitter.emit("BOOLS", event)
103
+ }
104
+ end
105
+ lines = out.readlines
106
+ assert_equal 1, lines.grep(/true = true;/).size
107
+ assert_equal 1, lines.grep(/false = false;/).size
108
+ end
109
+
110
+ def test_emit_numeric_ranges
111
+ check_min_max(:int16, -0x7fff - 1, 0x7fff)
112
+ check_min_max(:int32, -0x7fffffff - 1, 0x7fffffff)
113
+ check_min_max(:int64, -0x7fffffffffffffff - 1, 0x7fffffffffffffff)
114
+ check_min_max(:uint16, 0, 0xffff)
115
+ check_min_max(:uint32, 0, 0xffffffff)
116
+ check_min_max(:uint64, 0, 0xffffffffffffffff)
117
+ huge = 0xffffffffffffffffffffffffffffffff
118
+ tiny = -0xffffffffffffffffffffffffffffffff
119
+ [ :int16, :int32, :int64, :uint16, :uint32, :uint64 ].each do |type|
120
+ emitter = LWES::Emitter.new(@options)
121
+ assert_raises(RangeError) {
122
+ emitter.emit("way over", { type => [ type, huge ] })
123
+ }
124
+ assert_raises(RangeError) {
125
+ emitter.emit("way under", { type => [ type, tiny ] })
126
+ }
127
+ end
128
+ end
129
+
130
+ def check_min_max(type, min, max)
131
+ emitter = LWES::Emitter.new(@options)
132
+ out = lwes_listener do
133
+ assert_raises(RangeError) {
134
+ emitter.emit("over", { type => [ type, max + 1] })
135
+ }
136
+ assert_raises(RangeError, "type=#{type} min=#{min}") {
137
+ emitter.emit("under", { type => [ type, min - 1 ] })
138
+ }
139
+ assert_nothing_raised {
140
+ emitter.emit("zero", { type => [ type, 0 ] })
141
+ }
142
+ assert_nothing_raised {
143
+ emitter.emit("min", { type => [ type, min ] })
144
+ }
145
+ assert_nothing_raised {
146
+ emitter.emit("max", { type => [ type, max ] })
147
+ }
148
+ end
149
+ lines = out.readlines
150
+ assert lines.grep(/\Aover/).empty?
151
+ assert lines.grep(/\Aunder/).empty?
152
+ assert_equal 1, lines.grep(/\Amax/).size
153
+ assert_equal 1, lines.grep(/\Amin/).size
154
+ assert_equal 1, lines.grep(/\Azero/).size
155
+ end
156
+
157
+ def test_emit_uint64
158
+ emitter = LWES::Emitter.new(@options)
159
+ assert_nothing_raised {
160
+ emitter.emit("Foo", { :uint64 => [ :uint64, 10_000_000_000 ] })
161
+ }
162
+ end
163
+
164
+ def test_close
165
+ emitter = LWES::Emitter.new(@options)
166
+ assert_nil emitter.close
167
+ end
168
+
169
+ end