agent_fix 0.1.0

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