agent_fix 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,29 @@
1
+ $: << File.expand_path("../../../lib", __FILE__)
2
+
3
+ require 'agent_fix/cucumber'
4
+ require 'agent_fix/cucumber/report'
5
+ require 'fix_spec/cucumber'
6
+ require 'rspec'
7
+ require 'anticipate'
8
+
9
+ Around('@inspect_all') do |scenario, block|
10
+ old_scope = AgentFIX.include_session_level?
11
+ AgentFIX::include_session_level = true
12
+
13
+ #hard reset, forces logout/logon
14
+ AgentFIX.hard_reset
15
+ block.call
16
+ AgentFIX::include_session_level = old_scope
17
+ end
18
+
19
+ Before('~@inspect_all') do
20
+ AgentFIX.reset
21
+ end
22
+
23
+ FIXSpec::data_dictionary= quickfix.DataDictionary.new "features/support/FIX42.xml"
24
+
25
+ World(Anticipate)
26
+
27
+ AgentFIX.start
28
+ at_exit {AgentFIX.stop}
29
+
@@ -0,0 +1,157 @@
1
+ require 'thread'
2
+ require 'agent_fix/message_cache'
3
+
4
+ module AgentFIX
5
+ class Agent
6
+ include quickfix.Application
7
+
8
+ attr_reader :name, :connection_type
9
+ attr_accessor :default, :session
10
+
11
+ attr_accessor :bookmark
12
+
13
+ def initialize name, connection_type
14
+ @name = name
15
+ @connection_type = connection_type
16
+ @logged_on = false
17
+ @bookmark = 0
18
+ @all_messages = MessageCache.new
19
+ @logger = Java::org.slf4j.LoggerFactory.getLogger("AgentFIX.Agent")
20
+ end
21
+
22
+ def init
23
+ parse_settings
24
+ @connector = case @connection_type
25
+ when :acceptor then quickfix.SocketAcceptor.new(self, @storeFactory, @settings, @logFactory, @messageFactory)
26
+ when :initiator then quickfix.SocketInitiator.new(self, @storeFactory, @settings, @logFactory, @messageFactory)
27
+ else nil
28
+ end
29
+ end
30
+
31
+ def onCreate(sessionId)
32
+ @default_session = sessionId
33
+ end
34
+
35
+ def onLogon(sessionId)
36
+ @logger.debug "#{@name} onLogon: #{sessionId.to_s}"
37
+
38
+ lock.synchronize do
39
+ @logged_on = true
40
+ end
41
+ end
42
+
43
+ def onLogout(sessionId)
44
+ @logger.debug "#{@name} onLogout: #{sessionId.to_s}"
45
+
46
+ lock.synchronize do
47
+ @logged_on = false
48
+ end
49
+ end
50
+
51
+ def toApp(message, sessionId)
52
+ @logger.debug "#{@name} toApp #{sessionId.to_s}: #{message.to_s.gsub("","|")}"
53
+ @all_messages.add_message(message:message,sent:true,admin:false)
54
+ end
55
+
56
+ def fromApp(message, sessionId)
57
+ @logger.debug "#{@name} fromApp #{sessionId.to_s}: #{message.to_s.gsub("","|")}"
58
+ @all_messages.add_message(message:message,sent:false,admin:false)
59
+ end
60
+
61
+ def toAdmin(message, sessionId)
62
+ @logger.debug "#{@name} toAdmin #{sessionId.to_s}: #{message.to_s.gsub("","|")}"
63
+ @all_messages.add_message(message:message,sent:true,admin:true)
64
+ end
65
+
66
+ def fromAdmin(message, sessionId)
67
+ @logger.debug "#{@name} fromAdmin #{sessionId.to_s}: #{message.to_s.gsub("","|")}"
68
+ @all_messages.add_message(message:message,sent:false,admin:true)
69
+ end
70
+
71
+ def loggedOn?
72
+ lock.synchronize do
73
+ return @logged_on
74
+ end
75
+ end
76
+
77
+ def sendToTarget msg
78
+ msg.getHeader.setField(quickfix.field.BeginString.new(@default_session.getBeginString))
79
+ msg.getHeader.setField(quickfix.field.TargetCompID.new(@default_session.getTargetCompID))
80
+ msg.getHeader.setField(quickfix.field.SenderCompID.new(@default_session.getSenderCompID))
81
+
82
+ quickfix.Session.sendToTarget(msg)
83
+ end
84
+
85
+ def reset
86
+ clear_state!
87
+ end
88
+
89
+ def start
90
+ @connector.start
91
+ end
92
+
93
+ def stop
94
+ @connector.stop
95
+ clear_state!
96
+ end
97
+
98
+ def history opts={}
99
+ opts[:since] ||= 0
100
+ opts[:received_only] ||= false
101
+ opts[:include_session]||= AgentFIX::include_session_level?
102
+
103
+ indexed_msgs = []
104
+ @all_messages.messages.each_with_index { |m,i| indexed_msgs << m.merge(index:i) }
105
+ indexed_msgs = indexed_msgs.slice(opts[:since], indexed_msgs.length)
106
+
107
+ if opts[:received_only]
108
+ indexed_msgs = indexed_msgs.find_all {|m| !m[:sent]}
109
+ end
110
+
111
+ unless opts[:include_session]
112
+ indexed_msgs = indexed_msgs.find_all {|m| !m[:admin]}
113
+ end
114
+
115
+ indexed_msgs
116
+ end
117
+
118
+ def messages_received opts = {}
119
+ history opts.merge(:received_only=>true)
120
+ end
121
+
122
+ protected
123
+
124
+ def clear_state!
125
+ @all_messages.clear!
126
+ @bookmark = 0
127
+ end
128
+
129
+ def parse_settings
130
+ session_settings = "[DEFAULT]\n"
131
+ session_settings << "ConnectionType=#{@connection_type}\n"
132
+
133
+ @default ||= {}
134
+ AgentFIX::session_defaults.merge(@default).each do |k,v|
135
+ session_settings << "#{k}=#{v}\n"
136
+ end
137
+
138
+ unless @session.nil?
139
+ session_settings << "[SESSION]\n"
140
+ @session.each { |k,v| session_settings << "#{k}=#{v}\n"}
141
+ end
142
+
143
+ @logger.info "Settings for #{@name}: #{session_settings}"
144
+
145
+ @storeFactory = quickfix.MemoryStoreFactory.new()
146
+ @messageFactory = quickfix.DefaultMessageFactory.new()
147
+ @settings = quickfix.SessionSettings.new( Java::java.io.ByteArrayInputStream.new(session_settings.to_java_bytes) )
148
+ @logFactory = quickfix.FileLogFactory.new(@settings)
149
+ end
150
+
151
+ private
152
+
153
+ def lock
154
+ @lock||=Mutex.new
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,46 @@
1
+ module AgentFIX
2
+ module Configuration
3
+ REASONABLE_SESSION_DEFAULTS = {
4
+ StartTime: "00:00:00",
5
+ EndTime: "00:00:00",
6
+ FileLogPath: "fixlog",
7
+ HeartBtInt: 60
8
+ }
9
+
10
+ def include_session_level=(opt)
11
+ @include_session_level = opt
12
+ end
13
+
14
+ def include_session_level?
15
+ @include_session_level ||=false
16
+ end
17
+
18
+ def cucumber_sleep_seconds=(secs)
19
+ @cucumber_sleep_seconds = secs
20
+ end
21
+
22
+ def cucumber_sleep_seconds
23
+ @cucumber_sleep_seconds ||= 0.5
24
+ end
25
+
26
+ def cucumber_retries=(retries)
27
+ @cucumber_retries = retries
28
+ end
29
+
30
+ def cucumber_retries
31
+ @cucumber_retries ||= 10
32
+ end
33
+
34
+ def session_defaults=(defaults)
35
+ @session_defaults = defaults
36
+ end
37
+
38
+ def session_defaults
39
+ @session_defaults ||= REASONABLE_SESSION_DEFAULTS
40
+ end
41
+
42
+ def reset_config
43
+ instance_variables.each{|ivar| remove_instance_variable(ivar) }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,79 @@
1
+ class String
2
+ # colorization
3
+ def colorize(color_code)
4
+ "\e[#{color_code}m#{self}\e[0m"
5
+ end
6
+
7
+ def red
8
+ colorize(31)
9
+ end
10
+
11
+ def green
12
+ colorize(32)
13
+ end
14
+
15
+ def yellow
16
+ colorize(33)
17
+ end
18
+
19
+ def pink
20
+ colorize(35)
21
+ end
22
+
23
+ def white
24
+ colorize(37)
25
+ end
26
+
27
+ def blue
28
+ colorize(34)
29
+ end
30
+
31
+ def magenta
32
+ colorize(35)
33
+ end
34
+
35
+ def cyan
36
+ colorize(36)
37
+ end
38
+ end
39
+
40
+ def print_results agent
41
+ STDERR.puts "\nMessages for ".yellow + agent.name.to_s.white + ": ".yellow
42
+
43
+ agent.history(:include_session=>true).each do |msg|
44
+ if msg[:sent]
45
+ STDERR.puts "\tsent >>\t #{msg[:message].to_s.gsub!(/\001/, '|')}".green
46
+ else
47
+ outbound = "\trecv <<\t #{msg[:message].to_s.gsub!(/\001/, '|')}"
48
+
49
+ if @message!=nil and msg[:message] == @message
50
+ STDERR.puts outbound.red
51
+ else
52
+ if msg[:index] >= @agent.bookmark
53
+ STDERR.puts outbound.blue
54
+ else
55
+
56
+ if @message_scope.include? msg[:message]
57
+ STDERR.puts outbound.pink
58
+ else
59
+ STDERR.puts outbound.green
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ After do |scenario|
68
+ if scenario.failed? then
69
+ #last selected agent gets priority
70
+ unless @agent.nil?
71
+ print_results(@agent)
72
+ end
73
+
74
+ AgentFIX.agents_hash.values.each do |agent|
75
+ next if agent == @agent
76
+ print_results(agent)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,146 @@
1
+ require File.expand_path("../../agent_fix", __FILE__)
2
+
3
+ require 'fix_spec/builder'
4
+ require 'fix_spec/cucumber'
5
+
6
+ module FIXMessageCache
7
+ # accessor for fix_spec
8
+ def last_fix
9
+ @message
10
+ end
11
+
12
+ def recall_agent agent
13
+ agent = AgentFIX.agents_hash[agent.to_sym]
14
+ throw "Unknown agent #{agent}" if agent.nil?
15
+
16
+ agent
17
+ end
18
+ end
19
+
20
+ World(FIXMessageCache)
21
+
22
+ def anticipate_fix
23
+ sleeping(AgentFIX.cucumber_sleep_seconds).seconds.between_tries.failing_after(AgentFIX.cucumber_retries).tries do
24
+ yield
25
+ end
26
+ end
27
+
28
+ When(/^I send the following FIX message(?:s)? from agent "(.*?)":$/) do |agent, fix|
29
+ messages = fix.split("\n")
30
+
31
+ messages.each do |msg|
32
+ steps %Q{
33
+ Given the following fix message:
34
+ """
35
+ #{msg}
36
+ """
37
+ }
38
+
39
+ AgentFIX.agents_hash[agent.to_sym].sendToTarget(FIXSpec::Builder.message)
40
+ end
41
+ end
42
+
43
+ Then(/^I should receive (exactly )?(\d+)(?: FIX|fix)? messages(?: (?:on|over) FIX)?(?: of type "(.*?)")? with agent "(.*?)"$/) do |exact, count, type, agent|
44
+ @agent = recall_agent(agent)
45
+ count = count.to_i
46
+
47
+ scope = []
48
+ anticipate_fix do
49
+ messages = @agent.messages_received :since=>@agent.bookmark
50
+
51
+ if exact
52
+ (messages.length).should be == count, "Expected exactly #{count} messages, but got #{messages.length}"
53
+ else
54
+ (messages.length).should be >= count, "Expected #{count} messages, but got #{messages.length}"
55
+ end
56
+
57
+ scope=messages.slice(0, count)
58
+
59
+ unless type.nil?
60
+ unless FIXSpec::data_dictionary.nil?
61
+ type = FIXSpec::data_dictionary.get_msg_type(type)
62
+ end
63
+
64
+ @scope.each do |msg|
65
+ msg[:message].header.get_string(35).should == type
66
+ end
67
+ end
68
+ end
69
+
70
+ unless scope.empty?
71
+ @agent.bookmark = scope.last[:index]+1
72
+ end
73
+
74
+ @message_scope=scope.collect {|m| m[:message]}
75
+
76
+ #if we only requested one message for the scope, inspect that message
77
+ if count == 1
78
+ @message = @message_scope.first
79
+ else
80
+ @message = nil
81
+ end
82
+ end
83
+
84
+ Then(/^I should not receive any(?: more)?(?: FIX| fix)? messages with agent "(.*?)"$/) do |agent|
85
+ steps %Q{Then I should receive exactly 0 FIX messages with agent "#{agent}"}
86
+ end
87
+
88
+ Then(/^I should receive a(?: FIX| fix)? message(?: (?:on|over) FIX)?(?: of type "(.*?)")? with agent "(.*?)"$/) do |type, agent|
89
+ if type.nil?
90
+ steps %Q{Then I should receive 1 FIX messages with agent "#{agent}"}
91
+ else
92
+ steps %Q{Then I should receive 1 FIX messages of type "#{type}" with agent "#{agent}"}
93
+ end
94
+ end
95
+
96
+ When(/^I inspect the (\d+)(?:.*?)(?: FIX| fix)? message$/) do |index|
97
+ index = index.to_i-1
98
+
99
+ @message_scope.should_not be_nil, "No message scope defined"
100
+ @message_scope.length.should be >index, "There are only #{@message_scope.length} messages in the scope"
101
+ @message = @message_scope[index]
102
+ end
103
+
104
+
105
+ Then(/^the (\d+)(?:.*?)(?: FIX| fix)? message should have the following:$/) do |index, table|
106
+ table_raw =""
107
+ table.raw.each do |path, val|
108
+ table_raw << "|#{path}|#{val}|\n"
109
+ end
110
+
111
+ steps %Q{
112
+ When I inspect the #{index}th FIX message
113
+ Then the FIX message should have the following:
114
+ #{table_raw}
115
+ }
116
+ end
117
+
118
+ Then(/^the(?: FIX|fix)? messages should include(?: a message with)? the following:$/) do |table|
119
+ @message_scope.should_not be_nil, "No message scope defined"
120
+
121
+ table_raw =""
122
+ table.raw.each do |path, val|
123
+ table_raw << "|#{path}|#{val}|\n"
124
+ end
125
+
126
+ found = false
127
+ error_accum = ""
128
+ index = 1
129
+ @message_scope.each do |m|
130
+ @message = m
131
+ begin
132
+ steps %Q{
133
+ When I inspect the #{index}th FIX message
134
+ Then the FIX message should have the following:
135
+ #{table_raw}
136
+ }
137
+ found = true
138
+ rescue Exception => e
139
+ error_accum << "\n#{m.to_s.gsub!(/\001/, '|')}\n #{e}"
140
+ end
141
+ index += 1
142
+ end
143
+
144
+ found.should be_true, "Message not included in FIX messages\n #{error_accum}"
145
+
146
+ end
@@ -0,0 +1,31 @@
1
+ require 'thread'
2
+ module AgentFIX
3
+ class MessageCache
4
+ def messages
5
+ lock.synchronize do
6
+ return msgs.dup
7
+ end
8
+ end
9
+
10
+ def add_message msg
11
+ lock.synchronize do
12
+ msgs << msg
13
+ end
14
+ end
15
+
16
+ def clear!
17
+ lock.synchronize do
18
+ msgs.clear
19
+ end
20
+ end
21
+
22
+ private
23
+ def msgs
24
+ @messages||=[]
25
+ end
26
+
27
+ def lock
28
+ @lock||=Mutex.new
29
+ end
30
+ end
31
+ end
data/lib/agent_fix.rb ADDED
@@ -0,0 +1,75 @@
1
+ require 'quickfix'
2
+ require 'agent_fix/configuration'
3
+ require 'agent_fix/agent'
4
+
5
+ module AgentFIX
6
+ extend Configuration
7
+ extend self
8
+
9
+ def agent_path
10
+ "./config/fix_agents.rb"
11
+ end
12
+
13
+ def agents
14
+ return @agents if @agents
15
+
16
+ (@agents=[]).tap do
17
+ load_agents if agent_files_loaded.empty?
18
+ end
19
+ end
20
+
21
+ def agent_files_loaded
22
+ @agent_files_loaded ||=[]
23
+ end
24
+
25
+ def load_agents path=nil
26
+ path = File.expand_path(path || agent_path, Dir.pwd)
27
+ return if agent_files_loaded.include? path
28
+ agent_files_loaded << path
29
+ load path
30
+
31
+ agents.each {|a| a.init}
32
+ end
33
+
34
+ def define_agent(agent, &blk)
35
+ yield agent
36
+ agents << agent
37
+ end
38
+
39
+ def define_acceptor(name, &blk)
40
+ define_agent(Agent.new(name, :acceptor), &blk)
41
+ end
42
+
43
+ def define_initiator(name, &blk)
44
+ define_agent(Agent.new(name, :initiator), &blk)
45
+ end
46
+
47
+ #starts all configured agents
48
+ def start
49
+ raise RuntimeError, "No FIX Agents Defined" if agents.empty?
50
+
51
+ agents.each do |a|
52
+ a.start
53
+ end
54
+ end
55
+
56
+ def stop
57
+ agents.each {|a| a.stop}
58
+ end
59
+
60
+ def reset
61
+ agents.each {|a| a.reset}
62
+ end
63
+
64
+ def hard_reset
65
+ stop
66
+ sleep 0.5
67
+ start
68
+ end
69
+
70
+ def agents_hash
71
+ Hash[agents.map { |a| [a.name.to_sym, a]}]
72
+ end
73
+ end
74
+
75
+
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe AgentFIX::Configuration do
4
+ it "inspects just app messages by default" do
5
+ AgentFIX.include_session_level?.should be_false
6
+ end
7
+
8
+ it "can inspect both app and session level messages" do
9
+ AgentFIX.include_session_level = true
10
+ AgentFIX.include_session_level?.should be_true
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe AgentFIX::MessageCache do
4
+ it "is basically a read-only queue" do
5
+ cache = AgentFIX::MessageCache.new
6
+ cache.add_message "1"
7
+ cache.add_message "2"
8
+ cache.add_message "3"
9
+
10
+ msgs = cache.messages
11
+ msgs.should == ["1","2","3"]
12
+
13
+ cache.clear!
14
+ msgs.should == ["1","2","3"]
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require 'agent_fix'
2
+
3
+ RSpec.configure do |config|
4
+ config.before do
5
+ AgentFIX.reset_config
6
+ end
7
+ end
8
+