lwes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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