diff --git a/lib/isaac/bot.rb b/lib/isaac/bot.rb index 21fa31e..b7bf195 100644 --- a/lib/isaac/bot.rb +++ b/lib/isaac/bot.rb @@ -3,7 +3,7 @@ module Isaac VERSION = '0.2.1' - Config = Struct.new(:server, :port, :ssl, :password, :nick, :realname, :version, :environment, :verbose, :encoding) + Config = Struct.new(:server, :port, :ssl, :password, :nick, :realname, :version, :environment, :verbose, :encoding, :channels) class Bot attr_accessor :config, :irc, :nick, :channel, :message, :user, :host, :match, @@ -11,7 +11,7 @@ class Bot def initialize(&b) @events = {} - @config = Config.new("localhost", 6667, false, nil, "isaac", "Isaac", 'isaac', :production, false, "utf-8") + @config = Config.new("localhost", 6667, false, nil, "isaac", "Isaac", 'isaac', :production, false, "utf-8", []) instance_eval(&b) if block_given? end @@ -20,6 +20,12 @@ def configure(&b) b.call(@config) end + def configure_from(file) + cfgfile = ::File.read(file) + cfgfile.sub!(/^__END__\n.*/, '') + instance_eval( cfgfile ) + end + def on(event, match=//, &block) match = match.to_s if match.is_a? Integer (@events[event] ||= []) << [Regexp.new(match), block] @@ -71,7 +77,7 @@ def quit(message=nil) end def start - puts "Connecting to #{@config.server}:#{@config.port}" unless @config.environment == :test + $stdout.puts "Connecting to #{@config.server}:#{@config.port}" unless @config.environment == :test @irc = IRC.connect(self, @config) end @@ -118,9 +124,11 @@ def invoke(block) class IRC < EventMachine::Connection def self.connect(bot, config) - EventMachine.connect(config.server, config.port, self, bot, config) + EventMachine.connect(config.server, config.port, IRCClient, bot, config) end + end + module IRCClient def initialize(bot, config) @bot, @config = bot, config @transfered = 0 @@ -147,7 +155,7 @@ def receive_data(data) end def parse(input) - puts "<< #{input}" if @bot.config.verbose + $stdout.puts "<< #{input}" if @bot.config.verbose msg = Message.new(input) if ("001".."004").include? msg.command @@ -155,6 +163,7 @@ def parse(input) if registered? @queue.unlock @bot.dispatch(:connect) + @bot.join(*@bot.config.channels) unless @bot.config.channels.empty? end elsif msg.command == "PRIVMSG" if msg.params.last == "\001VERSION\001" diff --git a/test/helper.rb b/test/helper.rb old mode 100644 new mode 100755 index 8db722f..7cccb37 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,60 +1,105 @@ -$LOAD_PATH.unshift 'lib' -require 'isaac' -require 'rubygems' -require 'test/unit' -require 'contest' -require 'rr' -require 'timeout' -begin - require 'ruby-debug' -rescue LoadError; end - -module Test::Unit::Assertions - def assert_empty_buffer(io) - assert_raise(Errno::EAGAIN) { io.read_nonblock 1 } - end -end - -class MockSocket - def self.pipe - socket1, socket2 = new, new - socket1.in, socket2.out = IO.pipe - socket2.in, socket1.out = IO.pipe - [socket1, socket2] - end - - attr_accessor :in, :out - def gets() - Timeout.timeout(1) {@in.gets} - end - def puts(m) @out.puts(m) end - def print(m) @out.print(m) end - def eof?() @in.eof? end - def empty? - begin - @in.read_nonblock(1) - false - rescue Errno::EAGAIN - true - end - end -end - -class Test::Unit::TestCase - include RR::Adapters::TestUnit - - def mock_bot(&b) - @socket, @server = MockSocket.pipe - stub(TCPSocket).open(anything, anything) {@socket} - bot = Isaac::Bot.new(&b) - bot.config.environment = :test - Thread.start { bot.start } - bot - end - - def bot_is_connected - assert_equal "NICK isaac\r\n", @server.gets - assert_equal "USER isaac 0 * :Isaac\r\n", @server.gets - 1.upto(4) {|i| @server.print ":localhost 00#{i}\r\n"} - end -end +$LOAD_PATH.unshift 'lib' +require 'isaac/bot' +require 'rubygems' +require 'test/unit' +require 'contest' +require 'rr' +require 'timeout' +begin + require 'ruby-debug' +rescue LoadError; end + +class MockSocket + def self.pipe + socket1, socket2 = new, new + socket1.in, socket2.out = IO.pipe + socket2.in, socket1.out = IO.pipe + [socket1, socket2] + end + + attr_accessor :in, :out + def gets() + Timeout.timeout(1) {@in.gets} + end + def puts(m) @out.puts(m) end + def print(m) @out.print(m) end + def eof?() @in.eof? end + def empty? + begin + @in.read_nonblock(1) + false + rescue Errno::EAGAIN + true + end + end +end + + +class FakeReactor + + def poll(io, &blk) + (@polls ||= {})[io] = lambda { + blk.call(io.read_nonblock(1)) + } + end + + def react! + @polls.each do |_, proc| + loop do + begin + proc.call + rescue Errno::EAGAIN + break + end + end + end + end + +end + + +class StubIRCClient + include Isaac::IRCClient + + attr_accessor :socket + + def send_data data + @socket.print data + end + +end + + +class Test::Unit::TestCase + include RR::Adapters::TestUnit + + def mock_bot(bot=nil, &b) + @socket, @server = MockSocket.pipe + bot ||= Isaac::Bot.new(&b) + @r = FakeReactor.new + stub(Isaac::IRC).connect(anything, anything) do + conn = StubIRCClient.new(bot, bot.config) + conn.socket = @socket + conn.post_init + @r.poll(@socket.in) { |data| conn.receive_data data } + conn + end + bot.config.environment = :test + bot.start + bot + end + + def bot_is_connected + assert_equal "NICK isaac\r\n", @server.gets + assert_equal "USER isaac 0 * :Isaac\r\n", @server.gets + 1.upto(4) {|i| @server.print ":localhost 00#{i}\r\n" } + react! + end + + def react! + @r.react! + end + +end + + diff --git a/test/test_configure_from.rb b/test/test_configure_from.rb new file mode 100755 index 0000000..1ee23bc --- /dev/null +++ b/test/test_configure_from.rb @@ -0,0 +1,40 @@ +require 'helper' +require 'tempfile' + +class TestConfigureFrom < Test::Unit::TestCase + include Isaac + + def setup + @file = Tempfile.open('config') do |f| + f.write( +<<__EOF__ + config.server = "irc.foo.com" + config.nick = "foo" + config.realname = "Bar" +__EOF__ + ) + f + end + end + + def teardown + @file.unlink + end + + test "config is loaded from specified file" do + bot = Bot.new + + bot.configure_from(@file.path) + assert_equal "irc.foo.com", bot.config.server + assert_equal "foo", bot.config.nick + assert_equal "Bar", bot.config.realname + end + + test "default config is not changed" do + bot = Bot.new + + bot.configure_from(@file.path) + assert_equal 6667, bot.config.port + end + +end \ No newline at end of file diff --git a/test/test_events.rb b/test/test_events.rb index c282239..4bed2e7 100644 --- a/test/test_events.rb +++ b/test/test_events.rb @@ -86,4 +86,5 @@ def dispatch(type, env) assert_equal "foo\r\n", @server.gets end + end diff --git a/test/test_irc.rb b/test/test_irc.rb index 6cdf6b1..35e83bc 100644 --- a/test/test_irc.rb +++ b/test/test_irc.rb @@ -12,6 +12,7 @@ class TestIrc < Test::Unit::TestCase bot = mock_bot { configure {|c| c.password = "foo"} } + assert_equal "PASS foo\r\n", @server.gets end @@ -19,6 +20,7 @@ class TestIrc < Test::Unit::TestCase bot = mock_bot { on(:connect) {raw "Connected!"} } + 2.times { @server.gets } # NICK / USER bot.dispatch :connect @@ -32,7 +34,7 @@ class TestIrc < Test::Unit::TestCase 2.times { @server.gets } # NICK / USER bot.dispatch :connect - 1.upto(4) {|i| @server.puts ":localhost 00#{i}"} + 1.upto(4) {|i| @server.puts ":localhost 00#{i}"}; react! assert_equal "Connected!\r\n", @server.gets end end diff --git a/test/test_parse.rb b/test/test_parse.rb index 87e7f19..f841860 100644 --- a/test/test_parse.rb +++ b/test/test_parse.rb @@ -5,7 +5,7 @@ class TestParse < Test::Unit::TestCase bot = mock_bot {} bot_is_connected - @server.print "PING :foo.bar\r\n" + @server.print "PING :foo.bar\r\n"; react! assert_equal "PONG :foo.bar\r\n", @server.gets end @@ -15,7 +15,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":johnny!john@doe.com JOIN #foo\r\n" + @server.print ":johnny!john@doe.com JOIN #foo\r\n"; react! assert_equal "PRIVMSG #foo :bar baz\r\n", @server.gets end @@ -25,7 +25,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":johnny!john@doe.com PART #foo :Leaving\r\n" + @server.print ":johnny!john@doe.com PART #foo :Leaving\r\n"; react! assert_equal "PRIVMSG #foo :johnny left: Leaving\r\n", @server.gets end @@ -35,7 +35,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":johnny!john@doe.com QUIT :Leaving\r\n" + @server.print ":johnny!john@doe.com QUIT :Leaving\r\n"; react! assert_equal "PRIVMSG #foo :johnny quit: Leaving\r\n", @server.gets end @@ -45,7 +45,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":johnny!john@doe.com PRIVMSG isaac :hello, you!\r\n" + @server.print ":johnny!john@doe.com PRIVMSG isaac :hello, you!\r\n"; react! assert_equal "PRIVMSG foo :bar baz\r\n", @server.gets end @@ -55,7 +55,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":johnny!john@doe.com PRIVMSG #awesome :hello, folks!\r\n" + @server.print ":johnny!john@doe.com PRIVMSG #awesome :hello, folks!\r\n"; react! assert_equal "PRIVMSG foo :bar baz\r\n", @server.gets end @@ -65,7 +65,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print "PRIVMSG #awesome :hello, folks!\r\n" + @server.print "PRIVMSG #awesome :hello, folks!\r\n"; react! assert_equal "PRIVMSG foo :bar baz\r\n", @server.gets end @@ -80,7 +80,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.puts ":johnny!john@doe.com PRIVMSG isaac :hello, you!" + @server.puts ":johnny!john@doe.com PRIVMSG isaac :hello, you!"; react! assert_equal "johnny\r\n", @server.gets assert_equal "john\r\n", @server.gets assert_equal "doe.com\r\n", @server.gets @@ -99,7 +99,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.puts ":johnny!john@doe.com PRIVMSG #awesome :hello, folks!" + @server.puts ":johnny!john@doe.com PRIVMSG #awesome :hello, folks!"; react! assert_equal "johnny\r\n", @server.gets assert_equal "john\r\n", @server.gets assert_equal "doe.com\r\n", @server.gets @@ -115,7 +115,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":server 401 isaac jeff :No such nick/channel\r\n" + @server.print ":server 401 isaac jeff :No such nick/channel\r\n"; react! assert_equal "401\r\n", @server.gets end @@ -127,7 +127,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print "401 isaac jeff :No such nick/channel\r\n" + @server.print "401 isaac jeff :No such nick/channel\r\n"; react! assert_equal "401\r\n", @server.gets end @@ -137,7 +137,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":jeff!spicoli@name.com PRIVMSG isaac :\001VERSION\001\r\n" + @server.print ":jeff!spicoli@name.com PRIVMSG isaac :\001VERSION\001\r\n"; react! assert_equal "NOTICE jeff :\001VERSION Ridgemont 0.1\001\r\n", @server.gets end @@ -147,7 +147,7 @@ class TestParse < Test::Unit::TestCase } bot_is_connected - @server.print ":johnny!john@doe.com PRIVMSG #awesome :hello, folks!\r\n" + @server.print ":johnny!john@doe.com PRIVMSG #awesome :hello, folks!\r\n"; react! assert_equal "PRIVMSG foo :hello, folks! he said\r\n", @server.gets end end diff --git a/test/test_queue.rb b/test/test_queue.rb index 3fd5734..609c76c 100644 --- a/test/test_queue.rb +++ b/test/test_queue.rb @@ -19,7 +19,7 @@ def flood_bot test "ping after sending 1472 consequent bytes" do bot = flood_bot - bot.dispatch :connect + bot.dispatch(:connect) 16.times { @server.gets } assert_equal "PING :#{bot.config.server}\r\n", @server.gets assert @server.empty? @@ -32,7 +32,7 @@ def flood_bot 16.times { @server.gets } @server.gets # PING message - @server.puts ":localhost PONG :localhost" + @server.puts ":localhost PONG :localhost"; react! assert_equal "this should not flood!\r\n", @server.gets end @@ -42,7 +42,7 @@ def flood_bot bot.dispatch :connect 16.times { @server.gets } @server.gets # PING message triggered by transfer lock - @server.puts "PING :localhost" + @server.puts "PING :localhost"; react! assert_equal "this should not flood!\r\n", @server.gets end