class Talkshow

Main class for talking to a talkshow instrumented js application.

This is the only class you need to worry about, and there are only a few important methods.

Create the Talkshow client object:
  ts = Talkshow.new()
Start up the server
  ts.start_server()
Start executing javascript:
  ts.execute( 'alert "Hello world!"' )

Sinatra server that is launched by your test code to talk to the instrumented javascript application

Attributes

thread[RW]
type[RW]

Public Class Methods

new() click to toggle source

Create a new Talkshow object to get going:

# File lib/talkshow.rb, line 26
def initialize
end

Public Instance Methods

execute( command, timeout=6 ) click to toggle source

Send a javascript instruction to the client

# File lib/talkshow.rb, line 92
def execute( command, timeout=6 )
  send_question( { type: 'code', message: command }, timeout)
end
execute_file( filename ) click to toggle source

Load in a javascript file and execute remotely

# File lib/talkshow.rb, line 97
def execute_file( filename )
  text = File.read(filename)
  execute(text)
end
invoke( function, args, timeout=6 ) click to toggle source

Invoke a function in the javascript application invoke requires a function name (including the namespace). Arguments are specified as an array reference.

Some examples:

ts.invoke( 'alert' )
ts.invoke( 'alert', ['Hello world'])
ts.invoke( 'window.alert', ['Hello world'] )
# File lib/talkshow.rb, line 83
def invoke( function, args, timeout=6 )
  send_question( {
      type: 'invocation',
      function: function,
      args: args
    }, timeout)
end
start_server(options = {}) click to toggle source

Start up the Talkshow webserver This will be triggered if you don't do it – but it takes a few seconds to start up the thin server, so you are better off issuing this yourself

# File lib/talkshow.rb, line 33
def start_server(options = {})

  # Backward compatibility
  if options.is_a? String
    url = options
    port = nil
    logfile = nil
  else
    url = options[:url]
    port = options[:port]
    logfile = options[:logfile]
  end

  url = ENV['TALKSHOW_REMOTE_URL'] if ENV['TALKSHOW_REMOTE_URL']
  port = ENV['TALKSHOW_PORT'] if ENV['TALKSHOW_PORT']
  logfile = ENV['TALKSHOW_LOG'] if ENV['TALKSHOW_LOG']

  Talkshow::Server.set_port port if port
  Talkshow::Server.set_logfile logfile if logfile
  
  if !url
    @type = :thread
    @question_queue = ::Queue.new
    @answer_queue = ::Queue.new
    @thread = Thread.new do
      Talkshow::Server.question_queue(@question_queue)
      Talkshow::Server.answer_queue(@answer_queue)
      Talkshow::Server.run!
    end
  else
    @type = :remote
    @question_queue = Talkshow::Queue.new(url)
    @answer_queue = Talkshow::Queue.new(url)
  end
  
end
stop_server() click to toggle source

Stop the webserver

# File lib/talkshow.rb, line 71
def stop_server
  @thread.exit
end

Private Instance Methods

listen_for_answer(id, timeout) click to toggle source

listen for an answer for a specific id, with a timeout, and also reconstitute any chunked responses

# File lib/talkshow.rb, line 128
def listen_for_answer(id, timeout)
 
  if ENV['TIMEOUT_MULTIPLIER']
    timeout = ENV['TIMEOUT_MULTIPLIER'].to_i * timeout
  end

  answer = non_blocking_pop(timeout)
  if !answer
    raise Talkshow::Timeout.new
  end
  
  mismatch_retry = 3
  if answer[:id].to_i != id.to_i && mismatch_retry >= 0
    puts "Talkshow warning: message mismatch (#{answer[:id]} vs #{id})" unless answer[:id].to_i == 0
    answer = non_blocking_pop(timeout)
    mismatch_retry -= 1
  end

  if !answer
    raise Talkshow::Timeout.new
  end
  
  chunks = answer[:chunks]
  if chunks
    answers = [answer]

    i = 1
    nil_count = 0
    while ( i < chunks.to_i && nil_count < 3 ) do
      candidate = non_blocking_pop(1)
      if !candidate
        nil_count += 1
        next
      end
      if candidate[:id].to_i != id.to_i
        puts "Talkshow warning: message mismatch (#{candidate[:id]} vs #{id.to_i})"
        next
      end
      
      nil_count = 0
      i += 1
      answers << candidate
    end
    
    if answers.count < chunks.to_i
      raise "Couldn't reconstitute whole message"
    end
    
    sorted_answers = answers.sort_by{ |a| a[:payload].to_i }
    data = sorted_answers.collect { |a| a[:data] }.join
    answer[:data] = data
    answer[:payload] = nil
  end
      
  answer
end
non_blocking_pop(timeout) click to toggle source
# File lib/talkshow.rb, line 112
def non_blocking_pop(timeout)
  sleep_time = 0.1
  answer = nil
  catch(:done) do
    (timeout/sleep_time).to_i.times { |i|
      answer = soft_pop
      throw :done if answer
      sleep sleep_time
    }
  end
  answer
end
send_question( message, timeout ) click to toggle source

Send message to js application Message is a hash that looks like:

{
  type =>    message_type,
  message => command,
}

Timeout is optional, but a negative timeout returns without looking for an answer

# File lib/talkshow.rb, line 194
def send_question( message, timeout )
  
  # Start the server if it hasn't been started already
  self.start_server if (self.type == :thread && !self.thread)
  
  @answer_queue.clear();
  message[:id] = rand(99999)

  @question_queue.push( message )
  
  # Negative timeout - fire and forget
  # Should only be used if it is known not to return an answer
  return nil if timeout < 0

  answer = listen_for_answer(message[:id], timeout)

  if answer[:status] == 'error'
    raise Talkshow::JavascriptError.new( answer[:data] )
  end

  case answer[:object]
  when 'boolean'
    answer[:data] == 'true'
  when 'number'
    if answer[:data].include?('.')
      answer[:data].to_f
    else
      answer[:data].to_i
    end
  when 'undefined'
    if answer[:data] == 'undefined'
      nil
    else
      answer[:data]
    end
  when 'string'
    answer[:data].to_s
  else
    begin
      JSON.parse(answer[:data])
    rescue StandardError => e
      answer[:data]
    end
  end
end
soft_pop() click to toggle source
# File lib/talkshow.rb, line 104
def soft_pop
  begin
    @answer_queue.pop(true)
  rescue => e
    nil
  end
end