#! /usr/bin/ruby # # 15-441 Checkpoint 2 Script # # Please note that to pass specific tests in the checkpoint, # you must have passed the previous tests. For example, you cannot # pass the "PART" test if your "JOIN" test failed. # For this reason, the script exits once the server fails a test. # If you fail a specific test, we suggest you modify this code # to print out exactly # what is going on, or perhaps remove tests that # fail so that you can test other, unrelated tests if you simply haven't # implemented a particular bit of functionality yet (e.g., you might # remove the WHO or LIST tests to test PART). # # Part of the reason we give out the code for this # checkpoint so that you can modify it and write your own test # scripts later on. Remember that passing this will give # you full credit on the checkpoint, but does not ensure full credit # on the final IRC server grade: The final tests will be more extensive # than those in the checkpoint, so you may wish to add your own, # more stressful tests to this script for your own use. # # Enjoy! # # - 15-441 Staff ## SKELETON STOLEN FROM http://www.bigbold.com/snippets/posts/show/1785 require 'socket' $SAFE = 1 $SERVER = "127.0.0.1" $PORT = 14000 ########## DONT FORGET TO CHANGE THIS class IRC def initialize(server, port, nick, channel) @server = server @port = port @nick = nick @channel = channel end def recv_data_from_server (timeout) pending_event = Time.now.to_i received_data = Array.new k = 0 flag = 0 while flag == 0 ## check for timeout time_elapsed = Time.now.to_i - pending_event if (time_elapsed > timeout) flag = 1 end ready = select([@irc], nil, nil, 0.0001) next if !ready for s in ready[0] if s == @irc then next if @irc.eof s = @irc.gets received_data[k] = s k= k + 1 end end end return received_data end def test_silence(timeout) data=recv_data_from_server(timeout) if (data.size > 0) return false else return true end end def send(s) # Send a message to the irc server and print it to the screen puts "--> #{s}" @irc.send "#{s}\n", 0 end def connect() # Connect to the IRC server @irc = TCPSocket.open(@server, @port) end def disconnect() @irc.close end def send_nick(s) send("NICK #{s}") end def send_user(s) send("USER #{s}") end def get_motd data = recv_data_from_server(1) ## CHECK data here if(data[0] =~ /^:[^ ]+ *375 *gnychis *:- *[^ ]+ *Message of the day - *.\n/) puts "\tRPL_MOTDSTART 375 correct" else puts "\tRPL_MOTDSTART 375 incorrect" return false end k = 1 while ( k < data.size-1) if(data[k] =~ /:[^ ]+ *372 *gnychis *:- *.*/) puts "\tRPL_MOTD 372 correct" else puts "\tRPL_MOTD 372 incorrect" return false end k = k + 1 end if(data[data.size-1] =~ /:[^ ]+ *376 *gnychis *:End of \/MOTD command/) puts "\tRPL_ENDOFMOTD 376 correct" else puts "\tRPL_ENDOFMOTD 376 incorrect" return false end return true end def send_privmsg(s1, s2) send("PRIVMSG #{s1} :#{s2}") end def raw_join_channel(joiner, channel) send("JOIN #{channel}") ignore_reply() end def join_channel(joiner, channel) send("JOIN #{channel}") data = recv_data_from_server(1); if(data[0] =~ /^:#{joiner} *JOIN *#{channel}/) puts "\tJOIN echoed back" else puts "\tJOIN was not echoed back to the client" return false end if(data[1] =~ /^:[^ ]+ *353 *#{joiner} *= *#{channel} *:.*#{joiner}/) puts "\tRPL_NAMREPLY 353 correct" else puts "\tRPL_NAMREPLY 353 incorrect" return false end if(data[2] =~ /^:[^ ]+ *366 *#{joiner} *#{channel} *:End of \/NAMES list/) puts "\tRPL_ENDOFNAMES 366 correct" else puts "\tRPL_ENDOFNAMES 366 incorrect" return false end return true end def who(s) send("WHO #{s}") data = recv_data_from_server(1); if(data[0] =~ /^:[^ ]+ *352 *gnychis *#{s} *please *[^ ]+ *[^ ]+ *gnychis *H *:0 *The MOTD/) puts "\tRPL_WHOREPLY 352 correct" else puts "\tRPL_WHOREPLY 352 incorrect" return false end if(data[1] =~ /^:[^ ]+ *315 *gnychis *#{s} *:End of \/WHO list/) puts "\tRPL_ENDOFWHO 315 correct" else puts "\tRPL_ENDOFWHO 315 incorrect" return false end return true end def list send("LIST") data = recv_data_from_server(1); if(data[0] =~ /^:[^ ]+ *321 *gnychis *Channel *:Users Name/) puts "\tRPL_LISTSTART 321 correct" else puts "\tRPL_LISTSTART 321 incorrect" return false end if(data[1] =~ /^:[^ ]+ *322 *gnychis *#linux *:1/) puts "\tRPL_LIST 322 correct" else puts "\tRPL_LIST 322 incorrect" return false end if(data[2] =~ /^:[^ ]+ *323 *gnychis *:End of \/LIST/) puts "\tRPL_LISTEND 323 correct" else puts "\tRPL_LISTEND 323 incorrect" return false end return true end def checkmsg(from, to, msg) reply_matches(/^:#{from} *PRIVMSG *#{to} *:#{msg}/, "PRIVMSG") end def check2msg(from, to1, to2, msg) data = recv_data_from_server(1); if((data[0] =~ /^:#{from} *PRIVMSG *#{to1} *:#{msg}/ && data[1] =~ /^:#{from} *PRIVMSG *#{to2} *:#{msg}/) || (data[1] =~ /^:#{from} *PRIVMSG *#{to1} *:#{msg}/ && data[0] =~ /^:#{from} *PRIVMSG *#{to2} *:#{msg}/)) puts "\tPRIVMSG to #{to1} and #{to2} correct" return true else puts "\tPRIVMSG to #{to1} and #{to2} incorrect" return false end end def check_echojoin(from, channel) reply_matches(/^:#{from} *JOIN *#{channel}/, "Test if first client got join echo") end def part_channel(parter, channel) send("PART #{channel}") reply_matches(/^:#{parter}![^ ]+@[^ ]+ *QUIT *:/) end def check_part(parter, channel) reply_matches(/^:#{parter}![^ ]+@[^ ]+ *QUIT *:/) end def ignore_reply recv_data_from_server(1) end def reply_matches(rexp, explanation = nil) data = recv_data_from_server(1) if (rexp =~ data[0]) puts "\t #{explanation} correct" if explanation return true else puts "\t #{explanation} incorrect: #{data[0]}" if explanation return false end end end ## # The main program. Tests are listed below this point. All tests # should call the "result" function to report if they pass or fail. ## $total_points = 0 def test_name(n) puts "////// #{n} \\\\\\\\\\\\" return n end def result(n, passed_exp, failed_exp, passed, points) explanation = nil if (passed) print "(+) #{n} passed" $total_points += points explanation = passed_exp else print "(-) #{n} failed" explanation = failed_exp end if (explanation) puts ": #{explanation}" else puts "" end end def eval_test(n, passed_exp, failed_exp, passed, points = 1) result(n, passed_exp, failed_exp, passed, points) exit(0) if !passed end irc = IRC.new($SERVER, $PORT, '', '') irc.connect() begin ########## TEST NICK COMMAND ########################## # The RFC states that there is no response to a NICK command, # so we test for this. tn = test_name("NICK") irc.send_nick("gnychis") puts "<-- Testing for silence (5 seconds)..." eval_test(tn, nil, nil, irc.test_silence(5)) ############## TEST USER COMMAND ################## # The RFC states that there is no response on a USER, # so we disconnect first (otherwise the full registration # of NICK+USER would give us an MOTD), then check # for silence tn = test_name("USER") puts "Disconnecting and reconnecting to IRC server\n" irc.disconnect() irc.connect() irc.send_user("please give me :The MOTD") puts "<-- Testing for silence (5 seconds)..." eval_test(tn, nil, "should not return a response on its own", irc.test_silence(5)) ############# TEST FOR REGISTRATION ############## # A NICK+USER is a registration that triggers the # MOTD. This test sends a nickname to complete the registration, # and then checks for the MOTD. tn = test_name("Registration") irc.send_nick("gnychis") puts "<-- Listening for MOTD..."; eval_test(tn, nil, nil, irc.get_motd()) ############## TEST JOINING #################### # We join a channel and make sure the client gets # his join echoed back (which comes first), then # gets a names list. tn = test_name("JOIN") eval_test(tn, nil, nil, irc.join_channel("gnychis", "#linux")) ############## WHO #################### # Who should list everyone in a channel # or everyone on the server. We are only # checking WHO . # The response should follow the RFC. tn = test_name("WHO") eval_test(tn, nil, nil, irc.who("#linux")) ############## LIST #################### # LIST is used to check all the channels on a server. # The response should include #linux and its format should follow the RFC. tn = test_name("LIST") eval_test(tn, nil, nil, irc.list()) ############## PRIVMSG ################### # Connect a second client that sends a message to the original client. # Test that the original client receives the message. tn = test_name("PRIVMSG") irc2 = IRC.new($SERVER, $PORT, '', '') irc2.connect() irc2.send_nick("gnychis2") irc2.send_user("please give me :The MOTD2") msg = "clown hat curly hair smiley face" irc2.send_privmsg("gnychis", msg) eval_test(tn, nil, nil, irc.checkmsg("gnychis2", "gnychis", msg)) ############## MULTI-TARGET PRIVMSG ################### # A client should be able to send a single message to # multiple targets, with ',' as a delimiter. # We use client2 to send a message to gnychis and #linux. # Both should receive the message. tn = test_name("MULTI-TARGET PRIVMSG") msg = "awesome blossom with extra awesome" irc2.send_privmsg("gnychis,#linux", msg) eval_test(tn, nil, nil, irc.check2msg("gnychis2", "gnychis", "#linux", msg)) ############## ECHO JOIN ################### # When another client joins a channel, all clients # in that channel should get :newuser JOIN #channel tn = test_name("ECHO ON JOIN") # "raw" means no testing of responses done irc2.raw_join_channel("gnychis2", "#linux") irc2.ignore_reply() eval_test(tn, nil, nil, irc.check_echojoin("gnychis2", "#linux")) ############## PART ################### # When a client parts a channel, a QUIT message # is sent to all clients in the channel, including # the client that is parting. tn = test_name("PART") eval_test("PART echo to self", nil, nil, irc2.part_channel("gnychis2", "#linux"), 0) # note that this is a zero-point test! eval_test("PART echo to other clients", nil, nil, irc.check_part("gnychis2", "#linux")) ## Your tests go here! # # Things you might want to test: # - Multiple clients in a channel # - Abnormal messages of various sorts # - Clients that misbehave/disconnect/etc. # - Lots and lots of clients # - Lots and lots of channel switching # - etc. ## rescue Interrupt rescue Exception => detail puts detail.message() print detail.backtrace.join("\n") ensure irc.disconnect() puts "Your score: #{$total_points} / 10" puts "" puts "Good luck with the rest of the project!" puts "Remember to commit code into tags/checkpoint2" end