Init version of session unit tests.

This commit is contained in:
Martin Edenhofer 2014-06-27 08:43:37 +02:00
parent 2416f0ecde
commit 34a300238e
3 changed files with 482 additions and 144 deletions

View file

@ -18,6 +18,18 @@ module Sessions
@@user_threads = {}
@@client_threads = {}
=begin
start new session
Sessions.create( client_id, session_data, { :type => 'websocket' } )
returns
true|false
=end
def self.create( client_id, session, meta )
path = @path + '/' + client_id.to_s
FileUtils.mkpath path
@ -29,7 +41,6 @@ module Sessions
},
:meta => meta,
}
# puts 'CREATE' + Marshal.dump(data)
file.write Marshal.dump(data)
}
@ -42,6 +53,307 @@ module Sessions
end
end
=begin
list of all session
client_ids = Sessions.sessions
returns
['4711', '4712']
=end
def self.sessions
path = @path + '/'
# just make sure that spool path exists
if !File::exists?( path )
FileUtils.mkpath path
end
data = []
Dir.foreach( path ) do |entry|
next if entry == '.' || entry == '..' || entry == 'spool'
data.push entry.to_s
end
data
end
=begin
list of all session
Sessions.session_exists?(client_id)
returns
true|false
=end
def self.session_exists?(client_id)
client_ids = self.sessions
client_ids.include? client_id.to_s
end
=begin
list of all session with data
client_ids_with_data = Sessions.list
returns
{
'4711' => {
:user => {
:id => 123,
},
:meta => {
:type => 'websocket',
:last_ping => time_of_last_ping,
}
},
'4712' => {
:user => {
:id => 124,
},
:meta => {
:type => 'ajax',
:last_ping => time_of_last_ping,
}
},
}
=end
def self.list
client_ids = self.sessions
session_list = {}
client_ids.each { |client_id|
data = self.get(client_id)
next if !data
session_list[client_id] = data
}
session_list
end
=begin
destroy session
Sessions.destory?(client_id)
returns
true|false
=end
def self.destory( client_id )
path = @path + '/' + client_id.to_s
FileUtils.rm_rf path
end
=begin
destroy idle session
list_of_client_ids = Sessions.destory_idle_sessions
returns
['4711', '4712']
=end
def self.destory_idle_sessions(idle_time_in_min = 4)
list_of_closed_sessions = []
clients = Sessions.list
clients.each { |client_id, client|
if ( client[:meta][:last_ping].to_i + ( 60 * idle_time_in_min ) ) < Time.now.to_i
list_of_closed_sessions.push client_id
Sessions.destory( client_id )
end
}
list_of_closed_sessions
end
=begin
touch session
Sessions.touch(client_id)
returns
true|false
=end
def self.touch( client_id )
data = self.get(client_id)
return false if !data
path = @path + '/' + client_id.to_s
data[:meta][:last_ping] = Time.new.to_i.to_s
File.open( path + '/session', 'wb' ) { |file|
file.write Marshal.dump(data)
}
true
end
=begin
get session data
data = Sessions.get(client_id)
returns
{
:user => {
:id => 123,
},
:meta => {
:type => 'websocket',
:last_ping => time_of_last_ping,
}
}
=end
def self.get( client_id )
session_file = @path + '/' + client_id.to_s + '/session'
data = nil
return if !File.exist? session_file
begin
File.open( session_file, 'rb' ) { |file|
file.flock( File::LOCK_EX )
all = file.read
file.flock( File::LOCK_UN )
data = Marshal.load( all )
}
rescue Exception => e
File.delete(session_file)
puts "Error reading '#{session_file}':"
puts e.inspect
return
end
data
end
=begin
send message to client
Sessions.send(client_id_of_recipient, data)
returns
true|false
=end
def self.send( client_id, data )
path = @path + '/' + client_id.to_s + '/'
filename = 'send-' + Time.new().to_f.to_s# + '-' + rand(99999999).to_s
check = true
count = 0
while check
if File::exists?( path + filename )
count += 1
filename = filename + '-' + count
else
check = false
end
end
return false if !File.directory? path
File.open( path + 'a-' + filename, 'wb' ) { |file|
file.flock( File::LOCK_EX )
file.write data.to_json
file.flock( File::LOCK_UN )
file.close
}
return false if !File.exists?( path + 'a-' + filename )
FileUtils.mv( path + 'a-' + filename, path + filename )
true
end
=begin
send message to all client
Sessions.broadcast(data)
returns
true|false
=end
def self.broadcast( data )
# list all current clients
client_list = self.sessions
client_list.each {|client_id|
Sessions.send( client_id, data )
}
true
end
=begin
get messages for client
messages = Sessions.queue(client_id_of_recipient)
returns
[
{
key1 => 'some data of message 1',
key2 => 'some data of message 1',
},
{
key1 => 'some data of message 2',
key2 => 'some data of message 2',
},
]
=end
def self.queue( client_id )
path = @path + '/' + client_id.to_s + '/'
data = []
files = []
Dir.foreach( path ) {|entry|
next if entry == '.' || entry == '..'
files.push entry
}
files.sort.each {|entry|
filename = path + '/' + entry
if /^send/.match( entry )
data.push Sessions.queue_file_read( path, entry )
end
}
data
end
def self.queue_file_read( path, filename )
file_old = path + filename
file_new = path + 'a-' + filename
FileUtils.mv( file_old, file_new )
data = nil
all = ''
File.open( file_new, 'rb' ) { |file|
all = file.read
}
File.delete( file_new )
JSON.parse( all )
end
def self.spool_create( msg )
path = @path + '/spool/'
FileUtils.mkpath path
@ -51,7 +363,6 @@ module Sessions
:msg => msg,
:timestamp => Time.now.to_i,
}
# puts 'CREATE' + Marshal.dump(data)
file.write data.to_json
}
end
@ -120,75 +431,6 @@ module Sessions
return data
end
def self.list
client_ids = self.sessions
session_list = {}
client_ids.each { |client_id|
data = self.get(client_id)
next if !data
session_list[client_id] = data
}
return session_list
end
def self.touch( client_id )
data = self.get(client_id)
return if !data
path = @path + '/' + client_id.to_s
data[:meta][:last_ping] = Time.new.to_i.to_s
File.open( path + '/session', 'wb' ) { |file|
file.write Marshal.dump(data)
}
return true
end
def self.get( client_id )
session_file = @path + '/' + client_id.to_s + '/session'
data = nil
return if !File.exist? session_file
begin
File.open( session_file, 'rb' ) { |file|
file.flock( File::LOCK_EX )
all = file.read
file.flock( File::LOCK_UN )
data = Marshal.load( all )
}
rescue Exception => e
File.delete(session_file)
puts "Error reading '#{session_file}':"
puts e.inspect
return
end
return data
end
def self.send( client_id, data )
path = @path + '/' + client_id.to_s + '/'
filename = 'send-' + Time.new().to_f.to_s# + '-' + rand(99999999).to_s
check = true
count = 0
while check
if File::exists?( path + filename )
count += 1
filename = filename + '-' + count
# filename = filename + '-' + rand(99999).to_s
# filename = filename + '-' + rand(99999).to_s
else
check = false
end
end
return false if !File.directory? path
File.open( path + 'a-' + filename, 'wb' ) { |file|
file.flock( File::LOCK_EX )
file.write data.to_json
file.flock( File::LOCK_UN )
file.close
}
return false if !File.exists?( path + 'a-' + filename )
FileUtils.mv( path + 'a-' + filename, path + filename )
return true
end
def self.jobs
# just make sure that spool path exists
@ -248,12 +490,13 @@ module Sessions
def self.thread_worker(user_id, try_count = 0, try_run_time = Time.now)
puts "LOOP WORKER #{user_id} - #{try_count}"
begin
Sessions::Worker.new(user_id)
Sessions::Worker.new(user_id)
rescue => e
puts "thread_worker exited with error #{ e.inspect }"
sleep 10
begin
ActiveRecord::Base.connection.reconnect!
# ActiveRecord::Base.remove_connection
ActiveRecord::Base.connection_pool.reap
rescue => e
puts "Can't reconnect to database #{ e.inspect }"
end
@ -285,7 +528,8 @@ module Sessions
puts "thread_client exited with error #{ e.inspect }"
sleep 10
begin
ActiveRecord::Base.connection.reconnect!
# ActiveRecord::Base.remove_connection
ActiveRecord::Base.connection_pool.reap
rescue => e
puts "Can't reconnect to database #{ e.inspect }"
end
@ -309,66 +553,4 @@ module Sessions
puts "/LOOP #{client_id} - #{try_count}"
end
def self.sessions
path = @path + '/'
# just make sure that spool path exists
if !File::exists?( path )
FileUtils.mkpath path
end
data = []
Dir.foreach( path ) do |entry|
next if entry == '.' || entry == '..' || entry == 'spool'
data.push entry.to_s
end
return data
end
def self.queue( client_id )
path = @path + '/' + client_id.to_s + '/'
data = []
files = []
Dir.foreach( path ) {|entry|
next if entry == '.' || entry == '..'
files.push entry
}
files.sort.each {|entry|
filename = path + '/' + entry
if /^send/.match( entry )
data.push Sessions.queue_file( path, entry )
end
}
return data
end
def self.queue_file( path, filename )
file_old = path + filename
file_new = path + 'a-' + filename
FileUtils.mv( file_old, file_new )
data = nil
all = ''
File.open( file_new, 'rb' ) { |file|
all = file.read
}
File.delete( file_new )
data = JSON.parse( all )
return data
end
def self.broadcast( data )
# list all current clients
client_list = self.list
client_list.each {|local_client_id, local_client|
Sessions.send( local_client_id, data )
}
return true
end
def self.destory( client_id )
path = @path + '/' + client_id.to_s
FileUtils.rm_rf path
end
end

View file

@ -311,14 +311,10 @@ EventMachine.run {
end
}
# ajax
clients = Sessions.list
clients.each { |client_id, client|
next if client[:meta][:type] == 'websocket'
if ( client[:meta][:last_ping].to_i + ( 60 * idle_time_in_min ) ) < Time.now.to_i
log 'notice', "closing idle ajax connection", client_id
Sessions.destory( client_id )
end
# close unused sessions
clients = Sessions.destory_idle_sessions(idle_time_in_min)
clients.each { |client_id|
log 'notice', "closing idle connection", client_id
}
end

160
test/unit/session_test.rb Normal file
View file

@ -0,0 +1,160 @@
# encoding: utf-8
require 'test_helper'
class SessionTest < ActiveSupport::TestCase
test 'base' do
# create users
roles = Role.where( :name => [ 'Agent'] )
groups = Group.all
UserInfo.current_user_id = 1
agent1 = User.create_or_update(
:login => 'session-agent-1',
:firstname => 'Session',
:lastname => 'Agent 1',
:email => 'session-agent1@example.com',
:password => 'agentpw',
:active => true,
:roles => roles,
:groups => groups,
)
agent2 = User.create_or_update(
:login => 'session-agent-2',
:firstname => 'Session',
:lastname => 'Agent 2',
:email => 'session-agent2@example.com',
:password => 'agentpw',
:active => true,
:roles => roles,
:groups => groups,
)
agent3 = User.create_or_update(
:login => 'session-agent-3',
:firstname => 'Session',
:lastname => 'Agent 3',
:email => 'session-agent3@example.com',
:password => 'agentpw',
:active => true,
:roles => roles,
:groups => groups,
)
# create sessions
client_id1 = '1234'
client_id2 = '123456'
client_id3 = 'abc'
Sessions.destory(client_id1)
Sessions.destory(client_id2)
Sessions.destory(client_id3)
Sessions.create( client_id1, agent1.attributes, { :type => 'websocket' } )
Sessions.create( client_id2, agent2.attributes, { :type => 'ajax' } )
Sessions.create( client_id3, agent3.attributes, { :type => 'ajax' } )
# check if session exists
assert( Sessions.session_exists?(client_id1), "check if session exists" )
assert( Sessions.session_exists?(client_id2), "check if session exists" )
assert( Sessions.session_exists?(client_id3), "check if session exists" )
# check if session still exists after idle cleanup
sleep 1
Sessions.destory_idle_sessions(1)
assert( Sessions.session_exists?(client_id1), "check if session exists after 1 sec" )
assert( Sessions.session_exists?(client_id2), "check if session exists after 1 sec" )
assert( Sessions.session_exists?(client_id3), "check if session exists after 1 sec" )
# check client threads
# check worker threads
# check if session still exists after idle cleanup with touched sessions
sleep 62
Sessions.touch(client_id1)
Sessions.touch(client_id2)
Sessions.touch(client_id3)
Sessions.destory_idle_sessions(1)
assert( Sessions.session_exists?(client_id1), "check if session exists after touch" )
assert( Sessions.session_exists?(client_id2), "check if session exists after touch" )
assert( Sessions.session_exists?(client_id3), "check if session exists after touch" )
# check session data
data = Sessions.get(client_id1)
assert( data[:meta], "check if meta exists" )
assert( data[:user], "check if user exists" )
assert_equal( data[:user][:id], agent1.id, "check if user id is correct" )
data = Sessions.get(client_id2)
assert( data[:meta], "check if meta exists" )
assert( data[:user], "check if user exists" )
assert_equal( data[:user][:id], agent2.id, "check if user id is correct" )
data = Sessions.get(client_id3)
assert( data[:meta], "check if meta exists" )
assert( data[:user], "check if user exists" )
assert_equal( data[:user][:id], agent3.id, "check if user id is correct" )
# send data to one client
Sessions.send( client_id1, { :msg => 'äöüß123' } )
Sessions.send( client_id1, { :msg => 'äöüß1234' } )
messages = Sessions.queue(client_id1)
assert_equal( 3, messages.count, 'messages count')
assert_equal( 'ws:login', messages[0]['event'], 'messages 1')
assert_equal( true, messages[0]['data']['success'], 'messages 1')
assert_equal( 'äöüß123', messages[1]['msg'], 'messages 2')
assert_equal( 'äöüß1234', messages[2]['msg'], 'messages 3')
messages = Sessions.queue(client_id2)
assert_equal( messages.count, 1, 'messages count')
assert_equal( 'ws:login', messages[0]['event'], 'messages 1')
assert_equal( true, messages[0]['data']['success'], 'messages 1')
messages = Sessions.queue(client_id3)
assert_equal( messages.count, 1, 'messages count')
assert_equal( 'ws:login', messages[0]['event'], 'messages 1')
assert_equal( true, messages[0]['data']['success'], 'messages 1')
# broadcast to all clients
Sessions.broadcast( { :msg => 'ooo123123123123123123'} )
messages = Sessions.queue(client_id1)
assert_equal( messages.count, 1, 'messages count')
assert_equal( 'ooo123123123123123123', messages[0]['msg'], 'messages broadcast 1')
messages = Sessions.queue(client_id2)
assert_equal( messages.count, 1, 'messages count')
assert_equal( 'ooo123123123123123123', messages[0]['msg'], 'messages broadcast 1')
messages = Sessions.queue(client_id3)
assert_equal( messages.count, 1, 'messages count')
assert_equal( 'ooo123123123123123123', messages[0]['msg'], 'messages broadcast 1')
# check client threads
# check worker threads
# check if session still exists after idle cleanup
sleep 62
client_ids = Sessions.destory_idle_sessions(1)
# check client sessions
assert( !Sessions.session_exists?(client_id1), "check if session is removed" )
assert( !Sessions.session_exists?(client_id2), "check if session is removed" )
assert( !Sessions.session_exists?(client_id3), "check if session is removed" )
# check client threads
# check worker threads
end
end