spassky 0.1.33 → 0.1.34

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,53 +1,73 @@
1
- # Spassky #
2
- A distributed web testing tool. We use it at the BBC for testing our web apps on a wide range of mobile devices.
3
-
1
+ Spassky
2
+ =======
3
+ A distributed web testing tool. We use it at the BBC for testing our web apps on a wide range of devices.
4
4
 
5
5
  ![Spassky](https://github.com/BBC/spassky/raw/master/spassky.jpg)
6
6
 
7
- # Installation #
7
+ Installation
8
+ ------------
8
9
 
9
10
  ```
10
11
  gem install spassky
11
12
  ```
12
13
 
13
- # Usage #
14
+ Usage
15
+ -----
14
16
 
15
17
  Start the server:
16
18
 
17
19
  ```
18
- spassky-server
20
+ spassky server 9191
19
21
  ```
20
22
 
21
23
  Connect some devices by browsing to http://localhost:9191/device/connect on the device. The device will stay in an idle meta refresh loop until it receives a test to run.
22
24
 
23
25
  Check what devices are connected to the server:
24
- spassky http://localhost:9191 devices
26
+
27
+ ```
28
+ spassky devices http://localhost:9191
29
+ ```
25
30
 
26
31
  Run a test:
27
32
 
28
33
  ```
29
- spassky http://localhost:9191 html_test.html
34
+ spassky run html_test.html http://localhost:9191
30
35
  ```
31
36
 
32
37
  Run a test with colour:
33
38
 
34
39
  ```
35
- spassky http://localhost:9191 html_test.html --colour
40
+ spassky run html_test.html http://localhost:9191 --colour
36
41
  ```
37
42
 
38
43
  Run a directory that contains a test (the first .html file will be used as the test)
39
44
 
40
45
  ```
41
- spassky http://localhost:9191 test_directory
46
+ spassky run test_directory http://localhost:9191
42
47
  ```
43
48
 
44
- ## Why? ##
49
+ Why?
50
+ ----
45
51
  We need to run automated tests on a wide range of web-enabled devices with very mixed capabilities. Some of them have JavaScript, but in many cases it's not standard and buggy. That means web testing tools targeted at desktop browsers don't tend to work very well, if at all. Spassky uses legacy techniques and as little client-side JavaScript as possible to ensure we can run tests on as many devices as possible.
46
52
 
47
- ## How it works ##
53
+ How it works
54
+ ------------
48
55
  Physical devices act as test agents, connected permanently to a central server using meta refresh tags. Using a command-line utility, developers execute tests against those browsers by posting to the central server. The tests themselves are plain HTML pages, that are expected to call an assert URL (e.g. by embedding an image) within a time frame. That means even devices without any JavaScript can run automated tests of some kind.
49
56
 
50
- ## Some features that would be nice to have ##
51
- - Conveniently run QUnit / Jasmine / other tests
52
- - Run tests on a subset of agents
57
+ Test structure
58
+ --------------
59
+ Tests can be either a single html file, or a directory containing multiple files.
60
+
61
+ If spassky is given a directory, it will pass all files in that directory to that server. The first html file will be used as the test entry point. Any other files in the test directory can be linked to from the html file relatively.
62
+
63
+ ### An example test directory:
64
+ ```
65
+ test_name
66
+ |-scripts/main.js
67
+ |-css/main.css
68
+ |-test.html
69
+ ```
70
+
71
+ Some features that would be nice to have
72
+ ----------------------------------------
53
73
  - Assertions on network activity (e.g. for caching tests)
@@ -8,8 +8,10 @@ Feature: List Connected Devices
8
8
  And a connected mobile device "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3"
9
9
  And a connected mobile device "Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10"
10
10
  When I run "spassky devices <host>" with the server host
11
- Then the output should contain:
11
+ Then the output should contain exactly:
12
12
  """
13
13
  iPhone (id = apple_iphone_ver1_suba543, mobile_browser = Safari, device_os_version = 1.0)
14
14
  iPad (id = apple_ipad_ver1_sub5312110, mobile_browser = Safari, device_os_version = 3.2)
15
+
16
+
15
17
  """
@@ -41,6 +41,15 @@ Feature: Run HTML Tests
41
41
  """
42
42
  And the exit status should be 1
43
43
 
44
+ Scenario: One device with a user agent that exists in WURFL
45
+ Given a connected mobile device "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3"
46
+ When I run "spassky run passing.html <host>" with the server host
47
+ Then the output should contain:
48
+ """
49
+ PASS passing.html on iPhone (id = apple_iphone_ver1_suba543, mobile_browser = Safari, device_os_version = 1.0)
50
+ """
51
+ And the exit status should be 0
52
+
44
53
  Scenario: One passing test on one device
45
54
  Given a connected mobile device "blackberry"
46
55
  When I run "spassky run passing.html <host>" with the server host
@@ -73,3 +73,38 @@ Feature: Run QUnit Tests
73
73
  FAIL qunit_test on blackberry
74
74
  """
75
75
  And the exit status should be 1
76
+
77
+ Scenario: Support multiple levels of recursion
78
+ Given a file named "qunit_passing/qunit_test/another_directory/passing.js" with:
79
+ """
80
+ QUnit.done = function(result) {
81
+ if (result.failed > 0) {
82
+ assert(false, "qunit failed");
83
+ } else {
84
+ assert(true, "qunit passed");
85
+ }
86
+ };
87
+
88
+ test("it passes", function() {
89
+ ok(true, "it passed");
90
+ });
91
+ """
92
+ And a file named "qunit_passing/qunit_test/another_directory/qunit.js" with qunit.js in it
93
+ And a file named "qunit_passing/qunit_test/suite.html" with:
94
+ """
95
+ <html>
96
+ <head></head>
97
+ <body>
98
+ <h1>A QUnit Suite</h1>
99
+ <script type="text/javascript" src="another_directory/qunit.js"></script>
100
+ <script type="text/javascript" src="another_directory/passing.js"></script>
101
+ </body>
102
+ </html>
103
+ """
104
+ And a connected mobile device "blackberry"
105
+ When I run "spassky run qunit_passing/qunit_test <host>" with the server host
106
+ Then the output should contain:
107
+ """
108
+ PASS qunit_test on blackberry
109
+ """
110
+ And the exit status should be 0
@@ -18,7 +18,7 @@ module Spassky::Client
18
18
  def run(test, server = DEFAULT_SERVER, colour = false)
19
19
  writer = colour ? ColouredWriter : DefaultWriter
20
20
  pusher = Pusher.new(server)
21
- test_runner = TestRunner.new(pusher, writer.new(STDOUT), DirectoryReader.new)
21
+ test_runner = TestRunner.new(pusher, writer.new(STDOUT), DirectoryReader.new(test))
22
22
  test_runner.run_tests(test)
23
23
  end
24
24
 
@@ -27,6 +27,7 @@ module Spassky::Client
27
27
  Spassky::Client::DeviceListRetriever.new(server).get_connected_devices.each do |device|
28
28
  puts device
29
29
  end
30
+ nil
30
31
  end
31
32
 
32
33
  command "run the spassky server"
@@ -1,13 +1,30 @@
1
1
  module Spassky::Client
2
2
  class DirectoryReader
3
- def read_files(pattern)
4
- if File.file? pattern
5
- { pattern => File.read(pattern) }
6
- elsif File.directory? pattern
7
- Dir.glob(pattern + "/*").inject({}) do |hash, path|
8
- hash[File.basename(path)] = File.read(path)
9
- hash
3
+
4
+ def initialize(pattern)
5
+ @pattern = pattern
6
+ end
7
+
8
+ def read_directory
9
+ Dir.glob(@pattern + "/**/*").inject({}) do |hash, path|
10
+ if File.file? path
11
+ key = path.gsub(/^#{@pattern}\//, "")
12
+ hash[key] = File.read(path)
10
13
  end
14
+ hash
15
+ end
16
+
17
+ end
18
+
19
+ def read_file
20
+ { @pattern => File.read(@pattern) }
21
+ end
22
+
23
+ def read_files
24
+ if File.file? @pattern
25
+ read_file
26
+ elsif File.directory? @pattern
27
+ read_directory
11
28
  end
12
29
  end
13
30
  end
@@ -8,10 +8,6 @@ module Spassky::Client
8
8
  @sleeper = sleeper
9
9
  end
10
10
 
11
- def test_runs_url
12
- test_runs_url = @server_url.gsub(/\/$/, "") + "/test_runs"
13
- end
14
-
15
11
  def push(options)
16
12
  location = post_test(options)
17
13
  result = nil
@@ -24,14 +20,21 @@ module Spassky::Client
24
20
 
25
21
  private
26
22
 
23
+ def test_runs_url
24
+ test_runs_url = @server_url.gsub(/\/$/, "") + "/test_runs"
25
+ end
26
+
27
27
  def post_test(options)
28
- location = nil
29
28
  RestClient.post(test_runs_url, options) do |response, request, result|
30
- if response.code == 500
31
- raise response.to_str
32
- end
33
- location = response.headers[:location]
29
+ get_redirect_location response
30
+ end
31
+ end
32
+
33
+ def get_redirect_location response
34
+ if response.code == 500
35
+ raise response.to_str
34
36
  end
37
+ location = response.headers[:location]
35
38
  raise "Expected #{test_runs_url} to respond with 302" unless location
36
39
  location
37
40
  end
@@ -9,30 +9,29 @@ module Spassky::Client
9
9
  end
10
10
 
11
11
  def run_tests(pattern)
12
- previous_test_result = nil
13
12
  test_name = File.basename(pattern)
14
13
  begin
15
- @pusher.push(:name => test_name, :contents => @directory_reader.read_files(pattern).to_json) do |result|
16
- handle_test_result(previous_test_result, result)
17
- previous_test_result = result
18
- end
14
+ @pusher.push(:name => test_name, :contents => @directory_reader.read_files.to_json) do |result|
15
+ handle_test_result(result)
16
+ end
19
17
  rescue => error
20
18
  @writer.write_failing(error.message)
21
19
  Kernel.exit(1)
22
20
  end
23
21
  end
24
22
 
25
- def handle_test_result(previous_test_result, test_result)
26
- write_in_progress_status previous_test_result, test_result
23
+ def handle_test_result(test_result)
24
+ write_in_progress_status test_result
27
25
  unless test_result.status == "in progress"
28
26
  write(test_result.status, test_result.summary)
29
27
  end
28
+ @previous_test_result = test_result
30
29
  write_exit_code(test_result)
31
30
  end
32
31
 
33
- def write_in_progress_status previous_test_result, test_result
34
- test_result.completed_since(previous_test_result).each do |device_test_status|
35
- write(device_test_status.status, "#{device_test_status.status.upcase} #{device_test_status.test_name} on #{device_test_status.user_agent}")
32
+ def write_in_progress_status test_result
33
+ test_result.completed_since(@previous_test_result).each do |device_test_status|
34
+ write(device_test_status.status, "#{device_test_status.status.upcase} #{device_test_status.test_name} on #{device_test_status.device_id}")
36
35
  end
37
36
  end
38
37
 
@@ -24,15 +24,13 @@ module Spassky::Server
24
24
  redirect idle_url
25
25
  end
26
26
 
27
- def get_device_identifier user_agent
28
- SingletonDeviceDatabase.instance.device_identifier(user_agent)
27
+ def get_device_identifier
28
+ SingletonDeviceDatabase.instance.device_identifier(request.user_agent)
29
29
  end
30
30
 
31
31
  get '/device/idle/:random' do
32
- device_identifier = get_device_identifier(request.user_agent)
33
-
34
- test_run = TestRun.find_next_to_run_for_user_agent(device_identifier)
35
- @device_list.update_last_connected(device_identifier)
32
+ test_run = TestRun.find_next_test_to_run_by_device_id(get_device_identifier)
33
+ @device_list.update_last_connected(get_device_identifier)
36
34
  if test_run
37
35
  redirect_to_run_tests(test_run)
38
36
  else
@@ -40,15 +38,18 @@ module Spassky::Server
40
38
  end
41
39
  end
42
40
 
41
+ def create_test_run
42
+ TestRun.create({
43
+ :name => params[:name],
44
+ :contents => JSON.parse(params[:contents]),
45
+ :devices => @device_list.recently_connected_devices
46
+ })
47
+ end
48
+
43
49
  post '/test_runs' do
44
50
  recently_connected_devices = @device_list.recently_connected_devices
45
51
  if recently_connected_devices.size > 0
46
- run = TestRun.create({
47
- :name => params[:name],
48
- :contents => JSON.parse(params[:contents]),
49
- :devices => @device_list.recently_connected_devices
50
- })
51
- redirect "/test_runs/#{run.id}"
52
+ redirect "/test_runs/#{create_test_run.id}"
52
53
  else
53
54
  halt 500, "There are no connected devices"
54
55
  end
@@ -61,20 +62,24 @@ module Spassky::Server
61
62
  end
62
63
 
63
64
  get '/test_runs/:id/run/:random/assert' do
64
- TestRun.find(params[:id]).save_results_for_user_agent(
65
- :user_agent => request.user_agent,
65
+ TestRun.find(params[:id]).save_result_for_device(
66
+ :device_identifier => get_device_identifier,
66
67
  :status => params[:status]
67
68
  )
68
69
  end
69
70
 
70
- get '/test_runs/:id/run/:random/:file_name' do
71
- test_run = TestRun.find(params[:id])
72
- test_name = params[:file_name]
73
- HtmlTest.new(test_run.contents, idle_url, 1).get_file(test_name)
71
+ get "/test_runs/:id/run/:random/*" do
72
+ file_name = params[:splat].join("/")
73
+ get_test_file_contents params[:id], file_name
74
74
  end
75
75
 
76
76
  private
77
77
 
78
+ def get_test_file_contents test_run_id, file_name
79
+ test_run = TestRun.find(params[:id])
80
+ HtmlTest.new(test_run.contents, idle_url, 1).get_file(file_name)
81
+ end
82
+
78
83
  def redirect_to_run_tests(test_run)
79
84
  redirect "/test_runs/#{test_run.id}/run/#{RandomStringGenerator.random_string}/#{test_run.name}"
80
85
  end
@@ -85,8 +90,8 @@ module Spassky::Server
85
90
 
86
91
  def idle_page
87
92
  "<html><head><meta http-equiv=\"refresh\" content=\"1; url='#{idle_url}'\"></head>" +
88
- "<body>Idle #{RandomStringGenerator.random_string}</body>" +
89
- "</html>"
93
+ "<body>Idle #{RandomStringGenerator.random_string}</body>" +
94
+ "</html>"
90
95
  end
91
96
  end
92
97
  end
@@ -4,13 +4,13 @@ module Spassky::Server
4
4
  @devices_and_time_last_connected = {}
5
5
  end
6
6
 
7
- def update_last_connected user_agent
8
- @devices_and_time_last_connected[user_agent] = Time.now
7
+ def update_last_connected device_id
8
+ @devices_and_time_last_connected[device_id] = Time.now
9
9
  end
10
10
 
11
11
  def recently_connected_devices
12
- @devices_and_time_last_connected.keys.select do |user_agent|
13
- Time.now.to_f - @devices_and_time_last_connected[user_agent].to_f < 3
12
+ @devices_and_time_last_connected.keys.select do |device_id|
13
+ Time.now.to_f - @devices_and_time_last_connected[device_id].to_f < 3
14
14
  end
15
15
  end
16
16
 
@@ -7,34 +7,34 @@ module Spassky::Server
7
7
  def initialize(options)
8
8
  @name = options[:name]
9
9
  @contents = options[:contents]
10
- @status_by_user_agent = {}
10
+ @status_by_device_id = {}
11
11
  (options[:devices] || []).each do |device|
12
- @status_by_user_agent[device] = "in progress"
12
+ @status_by_device_id[device] = "in progress"
13
13
  end
14
14
  end
15
15
 
16
- def run_by_user_agent?(user_agent)
17
- @status_by_user_agent[user_agent] != "in progress"
16
+ def run_by_device_id?(device_id)
17
+ @status_by_device_id[device_id] != "in progress"
18
18
  end
19
19
 
20
- def save_results_for_user_agent(options)
20
+ def save_result_for_device(options)
21
21
  unless ['pass', 'fail'].include? options[:status]
22
22
  raise "#{options[:status]} is not a valid status"
23
23
  end
24
- @status_by_user_agent[options[:user_agent]] = options[:status]
24
+ @status_by_device_id[options[:device_identifier]] = options[:status]
25
25
  end
26
26
 
27
- def update_connected_devices(user_agents)
28
- @status_by_user_agent.each do |user_agent, status|
29
- if !user_agents.include?(user_agent) && status == "in progress"
30
- @status_by_user_agent[user_agent] = "timed out"
27
+ def update_connected_devices(device_ids)
28
+ @status_by_device_id.each do |device_id, status|
29
+ if !device_ids.include?(device_id) && status == "in progress"
30
+ @status_by_device_id[device_id] = "timed out"
31
31
  end
32
32
  end
33
33
  end
34
34
 
35
35
  def result
36
- Spassky::TestResult.new(@status_by_user_agent.map { |user_agent, status|
37
- Spassky::DeviceTestStatus.new(user_agent, status, name)
36
+ Spassky::TestResult.new(@status_by_device_id.map { |device_id, status|
37
+ Spassky::DeviceTestStatus.new(device_id, status, name)
38
38
  })
39
39
  end
40
40
 
@@ -49,8 +49,8 @@ module Spassky::Server
49
49
  test_runs.find { |test_run| test_run.id.to_s == id.to_s }
50
50
  end
51
51
 
52
- def self.find_next_to_run_for_user_agent(user_agent)
53
- test_runs.find { |test_run| test_run.run_by_user_agent?(user_agent) == false }
52
+ def self.find_next_test_to_run_by_device_id(device_id)
53
+ test_runs.find { |test_run| test_run.run_by_device_id?(device_id) == false }
54
54
  end
55
55
 
56
56
  def self.delete_all
@@ -34,7 +34,7 @@ module Spassky
34
34
  :status => "pass",
35
35
  :device_statuses => @device_statuses.map do |status|
36
36
  {
37
- :user_agent => status.user_agent,
37
+ :device_id => status.device_id,
38
38
  :status => status.status,
39
39
  :test_name => status.test_name
40
40
  }
@@ -46,7 +46,7 @@ module Spassky
46
46
  parsed = JSON.parse(json)
47
47
  test_result = TestResult.new(
48
48
  parsed['device_statuses'].map do |t|
49
- DeviceTestStatus.new(t["user_agent"], t["status"], t["test_name"])
49
+ DeviceTestStatus.new(t["device_id"], t["status"], t["test_name"])
50
50
  end
51
51
  )
52
52
  end
@@ -71,10 +71,10 @@ module Spassky
71
71
  end
72
72
 
73
73
  class DeviceTestStatus
74
- attr_reader :user_agent, :status, :test_name
74
+ attr_reader :device_id, :status, :test_name
75
75
 
76
- def initialize(user_agent, status, test_name)
77
- @user_agent = user_agent
76
+ def initialize(device_id, status, test_name)
77
+ @device_id = device_id
78
78
  @status = status
79
79
  @test_name = test_name
80
80
  end
@@ -1,3 +1,3 @@
1
1
  module Spassky
2
- VERSION = '0.1.33'
2
+ VERSION = '0.1.34'
3
3
  end
@@ -2,34 +2,72 @@ require 'spassky/client/directory_reader'
2
2
 
3
3
  module Spassky::Client
4
4
  describe DirectoryReader do
5
+ def stub_file path
6
+ File.stub!(:directory?).with(path).and_return(false)
7
+ File.stub!(:file?).with(path).and_return(true)
8
+ end
9
+
10
+ def stub_directory path
11
+ File.stub!(:directory?).with(path).and_return(true)
12
+ File.stub!(:file?).with(path).and_return(false)
13
+ end
14
+
15
+ def add_file path, contents
16
+ @all_paths ||= []
17
+ @all_paths << path
18
+ stub_file path
19
+ File.stub!(:read).with(path).and_return(contents)
20
+ end
21
+
22
+ def add_directory path
23
+ @all_paths ||= []
24
+ @all_paths << path
25
+ stub_directory path
26
+ end
27
+
5
28
  context "when given a file name" do
6
29
  it "returns a hash with one file" do
7
30
  File.should_receive(:file?).and_return(true)
8
31
  File.should_receive(:read).with("foo").and_return("content")
9
- DirectoryReader.new.read_files("foo").should == {
32
+ DirectoryReader.new("foo").read_files.should == {
10
33
  "foo" => "content"
11
34
  }
12
35
  end
13
36
  end
37
+
14
38
  context "when given a directory name" do
39
+ before do
40
+ stub_directory "directory"
41
+ @all_paths = []
42
+ Dir.stub!(:glob).and_return(@all_paths)
43
+ end
44
+
15
45
  it "globs for files in the specified directory" do
16
- File.stub!(:file?).and_return(false)
17
- File.stub!(:directory?).and_return(true)
18
- Dir.should_receive(:glob).with("directory/*").and_return([])
19
- DirectoryReader.new.read_files("directory")
46
+ Dir.should_receive(:glob).with("directory/**/*").and_return([])
47
+ DirectoryReader.new("directory").read_files
20
48
  end
21
49
 
50
+
51
+
22
52
  it "returns a hash with all files in that directory" do
23
- File.stub!(:file?).and_return(false)
24
- File.stub!(:directory?).and_return(true)
25
- Dir.stub!(:glob).and_return ["directory/file1", "directory/file2"]
26
- File.stub!(:read).with("directory/file1").and_return("file 1 contents")
27
- File.stub!(:read).with("directory/file2").and_return("file 2 contents")
28
- DirectoryReader.new.read_files("directory").should == {
53
+ add_file "directory/file1", "file 1 contents"
54
+ add_file "directory/file2", "file 2 contents"
55
+ DirectoryReader.new("directory").read_files.should == {
29
56
  "file1" => "file 1 contents",
30
57
  "file2" => "file 2 contents"
31
58
  }
32
59
  end
60
+
61
+ it "recursively finds files in a sub-directory" do
62
+ add_directory "directory/subdir"
63
+ add_file "directory/subdir/file.html", "file 1 contents"
64
+ add_file "directory/another_file.txt", "file 2 contents"
65
+
66
+ DirectoryReader.new("directory").read_files.should == {
67
+ "subdir/file.html" => "file 1 contents",
68
+ "another_file.txt" => "file 2 contents"
69
+ }
70
+ end
33
71
  end
34
72
  end
35
73
  end
@@ -124,8 +124,8 @@ module Spassky::Client
124
124
  @test_pusher.stub!(:push).and_yield(in_progress_one).and_yield(in_progress_two)
125
125
 
126
126
  in_progress_two.stub!(:completed_since).with(in_progress_one).and_return([
127
- mock(:status_one, :status => "pass", :user_agent => "ipad", :test_name => "foo"),
128
- mock(:status_one, :status => "fail", :user_agent => "iphone", :test_name => "bar")
127
+ mock(:status_one, :status => "pass", :device_id => "ipad", :test_name => "foo"),
128
+ mock(:status_one, :status => "fail", :device_id => "iphone", :test_name => "bar")
129
129
  ])
130
130
 
131
131
  @writer.should_receive(:write_passing).with("PASS foo on ipad").once
@@ -56,7 +56,7 @@ module Spassky::Server
56
56
 
57
57
  context "when there are no tests to run on the connected device" do
58
58
  it "serves HTML page with a meta-refresh tag" do
59
- TestRun.stub!(:find_next_to_run_for_user_agent).and_return(nil)
59
+ TestRun.stub!(:find_next_test_to_run_by_device_id).and_return(nil)
60
60
  RandomStringGenerator.should_receive(:random_string).and_return("next-iteration")
61
61
  get "/device/idle/123"
62
62
  last_response.body.should include("<meta http-equiv=\"refresh\" content=\"1; url='/device/idle/next-iteration'\">")
@@ -69,7 +69,7 @@ module Spassky::Server
69
69
  test = mock(:test, :contents => "test contents")
70
70
  test.stub!(:id).and_return("the-test-id")
71
71
  test.stub!(:name).and_return("the-test-name")
72
- TestRun.stub!(:find_next_to_run_for_user_agent).with("anything").and_return(test)
72
+ TestRun.stub!(:find_next_test_to_run_by_device_id).with("anything").and_return(test)
73
73
  get '/device/idle/123'
74
74
  last_response.should be_redirect
75
75
  last_response.location.should == 'http://example.org/test_runs/the-test-id/run/a-random-string/the-test-name'
@@ -79,19 +79,22 @@ module Spassky::Server
79
79
 
80
80
  describe "GET /test_runs/:id/run/:random/assert" do
81
81
  it "saves the test result" do
82
+ @device_database = mock(:device_database, :device_identifier => "the device identifier")
83
+ SingletonDeviceDatabase.stub!(:instance).and_return(@device_database)
82
84
  test = mock(:test)
83
85
  TestRun.stub!(:find).with('123').and_return(test)
84
- test.should_receive(:save_results_for_user_agent).with(:user_agent => "some user agent", :status => "pass")
86
+ test.should_receive(:save_result_for_device).with(:device_identifier => "the device identifier", :status => "pass")
85
87
  get "/test_runs/123/run/random/assert?status=pass"
86
88
  end
87
89
  end
88
90
 
89
- describe "GET /test_runs/:id/run/:random/filename" do
91
+ describe "GET /test_runs/:id/run/:random/path/to/file" do
90
92
  before do
91
93
  @test_contents = {
92
94
  "test_file.js" => "some javascript",
93
95
  "fake_test.html.file" => "don't choose this one",
94
- "test_name.html" => "actual test!"
96
+ "test_name.html" => "actual test!",
97
+ "directory/another_directory/filename.txt" => "file 1 contents"
95
98
  }
96
99
  end
97
100
 
@@ -113,6 +116,15 @@ module Spassky::Server
113
116
  end
114
117
  end
115
118
 
119
+ context "with a file that is in a subdirectory" do
120
+ it "returns the file" do
121
+ test = mock(:test, :name => "test_name", :contents => @test_contents)
122
+ TestRun.stub!(:find).and_return(test)
123
+ get "/test_runs/123/run/random/directory/another_directory/filename.txt"
124
+ last_response.body.should include "file 1 contents"
125
+ end
126
+ end
127
+
116
128
  describe "when the test contents includes a </head> tag" do
117
129
  before do
118
130
  @test_contents["test_name.html"] = "</head>"
@@ -6,13 +6,13 @@ module Spassky::Server
6
6
  before do
7
7
  TestRun.delete_all
8
8
  end
9
-
9
+
10
10
  it "can be created and retrieved" do
11
11
  created = TestRun.create(:name => "a test", :contents => "the contents")
12
12
  retrieved = TestRun.find(created.id)
13
13
  created.should == retrieved
14
14
  end
15
-
15
+
16
16
  it "assigns each test run a unique id" do
17
17
  test_run1 = TestRun.create(:name => "test run 1", :contents => "the contents")
18
18
  test_run2 =TestRun.create(:name => "test run 2", :contents => "the contents")
@@ -28,38 +28,38 @@ module Spassky::Server
28
28
 
29
29
  it "returns the next test to run for each device" do
30
30
  created = TestRun.create(:name => "test", :contents => "contents", :devices => ["x", "y"])
31
- TestRun.find_next_to_run_for_user_agent("x").should == created
32
- TestRun.find_next_to_run_for_user_agent("y").should == created
31
+ TestRun.find_next_test_to_run_by_device_id("x").should == created
32
+ TestRun.find_next_test_to_run_by_device_id("y").should == created
33
33
  end
34
-
34
+
35
35
  it "only returns a test run per user agent until results are saved" do
36
36
  created = TestRun.create(:name => "another test", :contents => "the contents of the test", :devices => ["user agent 1"])
37
- TestRun.find_next_to_run_for_user_agent("user agent 1").should == created
38
- TestRun.find_next_to_run_for_user_agent("user agent 1").should == created
39
- created.save_results_for_user_agent(:user_agent => "user agent 1", :status => "pass")
40
- TestRun.find_next_to_run_for_user_agent("user agent 1").should be_nil
37
+ TestRun.find_next_test_to_run_by_device_id("user agent 1").should == created
38
+ TestRun.find_next_test_to_run_by_device_id("user agent 1").should == created
39
+ created.save_result_for_device(:device_identifier => "user agent 1", :status => "pass")
40
+ TestRun.find_next_test_to_run_by_device_id("user agent 1").should be_nil
41
41
  end
42
-
42
+
43
43
  it "returns 'in progress' status per user agent until the results are saved" do
44
44
  created = TestRun.create(:name => "another test", :contents => "the contents of the test")
45
45
  created.result.status.should == 'in progress'
46
- created.save_results_for_user_agent(:user_agent => "some user agent", :status => "pass")
46
+ created.save_result_for_device(:device_identifier => "some user agent", :status => "pass")
47
47
  created.result.status.should == 'pass'
48
48
  end
49
-
49
+
50
50
  it "returns failed if the saved tests results failed" do
51
51
  created = TestRun.create(:name => "another test", :contents => "the contents of the test")
52
- created.save_results_for_user_agent(:user_agent => "another user agent", :status => "fail")
52
+ created.save_result_for_device(:device_identifier => "another user agent", :status => "fail")
53
53
  created.result.status.should == 'fail'
54
54
  end
55
-
55
+
56
56
  it "rejects nonsense status" do
57
57
  run = TestRun.create(:name => "another test", :contents => "the contents of the test")
58
58
  lambda {
59
- run.save_results_for_user_agent(:user_agent => "another user agent", :status => "wtf?")
59
+ run.save_result_for_device(:device_identifier => "another user agent", :status => "wtf?")
60
60
  }.should raise_error("wtf? is not a valid status")
61
61
  end
62
-
62
+
63
63
  it "creates an in progress test result from the device list" do
64
64
  run = TestRun.create(
65
65
  :name => "another test",
@@ -81,15 +81,15 @@ module Spassky::Server
81
81
  run.result.device_statuses.first.status.should == "in progress"
82
82
  run.result.device_statuses.last.status.should == "timed out"
83
83
  end
84
-
84
+
85
85
  it "does not update the status of disconnected devices that have already passed or failed" do
86
86
  run = TestRun.create(
87
87
  :name => "another test",
88
88
  :contents => "the contents of the test",
89
89
  :devices => ["device1", "device2"]
90
90
  )
91
- run.save_results_for_user_agent(:user_agent => "device1", :status => "pass")
92
- run.save_results_for_user_agent(:user_agent => "device2", :status => "fail")
91
+ run.save_result_for_device(:device_identifier => "device1", :status => "pass")
92
+ run.save_result_for_device(:device_identifier => "device2", :status => "fail")
93
93
  run.update_connected_devices([])
94
94
  run.result.device_statuses.first.status.should == "pass"
95
95
  run.result.device_statuses.last.status.should == "fail"
@@ -60,7 +60,7 @@ module Spassky
60
60
  json = test_result.to_json
61
61
  deserialized = TestResult.from_json(json)
62
62
  deserialized.device_statuses.size.should == 1
63
- deserialized.device_statuses.first.user_agent.should == 'agent'
63
+ deserialized.device_statuses.first.device_id.should == 'agent'
64
64
  deserialized.device_statuses.first.status.should == 'pass'
65
65
  end
66
66
 
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
+ ENV['RACK_ENV'] = 'test'
1
2
  $:.unshift(File.dirname(__FILE__) + '/../lib')
2
3
  $:.unshift(File.dirname(__FILE__))
3
- require 'spassky'
4
+ require 'spassky'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spassky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.33
4
+ version: 0.1.34
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,11 +11,11 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2011-09-02 00:00:00.000000000Z
14
+ date: 2011-09-08 00:00:00.000000000Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rest-client
18
- requirement: &2168560600 !ruby/object:Gem::Requirement
18
+ requirement: &70225077309240 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ! '>='
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: '0'
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *2168560600
26
+ version_requirements: *70225077309240
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: json
29
- requirement: &2168560040 !ruby/object:Gem::Requirement
29
+ requirement: &70225077308820 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ! '>='
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: '0'
35
35
  type: :runtime
36
36
  prerelease: false
37
- version_requirements: *2168560040
37
+ version_requirements: *70225077308820
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: sinatra
40
- requirement: &2168559360 !ruby/object:Gem::Requirement
40
+ requirement: &70225077308400 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ! '>='
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: '0'
46
46
  type: :runtime
47
47
  prerelease: false
48
- version_requirements: *2168559360
48
+ version_requirements: *70225077308400
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: rainbow
51
- requirement: &2168558800 !ruby/object:Gem::Requirement
51
+ requirement: &70225077307920 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ! '>='
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: '0'
57
57
  type: :runtime
58
58
  prerelease: false
59
- version_requirements: *2168558800
59
+ version_requirements: *70225077307920
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: wurfl-lite
62
- requirement: &2168558160 !ruby/object:Gem::Requirement
62
+ requirement: &70225077307440 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ! '>='
@@ -67,10 +67,10 @@ dependencies:
67
67
  version: '0'
68
68
  type: :runtime
69
69
  prerelease: false
70
- version_requirements: *2168558160
70
+ version_requirements: *70225077307440
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: commandable
73
- requirement: &2168557360 !ruby/object:Gem::Requirement
73
+ requirement: &70225077271520 !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
76
  - - ! '>='
@@ -78,10 +78,10 @@ dependencies:
78
78
  version: '0'
79
79
  type: :runtime
80
80
  prerelease: false
81
- version_requirements: *2168557360
81
+ version_requirements: *70225077271520
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: rake
84
- requirement: &2168556680 !ruby/object:Gem::Requirement
84
+ requirement: &70225077270840 !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
87
87
  - - ! '>='
@@ -89,10 +89,10 @@ dependencies:
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
- version_requirements: *2168556680
92
+ version_requirements: *70225077270840
93
93
  - !ruby/object:Gem::Dependency
94
94
  name: rspec
95
- requirement: &2168556160 !ruby/object:Gem::Requirement
95
+ requirement: &70225077270240 !ruby/object:Gem::Requirement
96
96
  none: false
97
97
  requirements:
98
98
  - - ! '>='
@@ -100,10 +100,10 @@ dependencies:
100
100
  version: '0'
101
101
  type: :development
102
102
  prerelease: false
103
- version_requirements: *2168556160
103
+ version_requirements: *70225077270240
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: cucumber
106
- requirement: &2168555260 !ruby/object:Gem::Requirement
106
+ requirement: &70225077269620 !ruby/object:Gem::Requirement
107
107
  none: false
108
108
  requirements:
109
109
  - - ! '>='
@@ -111,10 +111,10 @@ dependencies:
111
111
  version: '0'
112
112
  type: :development
113
113
  prerelease: false
114
- version_requirements: *2168555260
114
+ version_requirements: *70225077269620
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: capybara
117
- requirement: &2168554560 !ruby/object:Gem::Requirement
117
+ requirement: &70225077269000 !ruby/object:Gem::Requirement
118
118
  none: false
119
119
  requirements:
120
120
  - - ! '>='
@@ -122,10 +122,10 @@ dependencies:
122
122
  version: '0'
123
123
  type: :development
124
124
  prerelease: false
125
- version_requirements: *2168554560
125
+ version_requirements: *70225077269000
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: aruba
128
- requirement: &2168553980 !ruby/object:Gem::Requirement
128
+ requirement: &70225077268580 !ruby/object:Gem::Requirement
129
129
  none: false
130
130
  requirements:
131
131
  - - ! '>='
@@ -133,10 +133,10 @@ dependencies:
133
133
  version: '0'
134
134
  type: :development
135
135
  prerelease: false
136
- version_requirements: *2168553980
136
+ version_requirements: *70225077268580
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: fakefs
139
- requirement: &2168553200 !ruby/object:Gem::Requirement
139
+ requirement: &70225077268160 !ruby/object:Gem::Requirement
140
140
  none: false
141
141
  requirements:
142
142
  - - ! '>='
@@ -144,10 +144,10 @@ dependencies:
144
144
  version: '0'
145
145
  type: :development
146
146
  prerelease: false
147
- version_requirements: *2168553200
147
+ version_requirements: *70225077268160
148
148
  - !ruby/object:Gem::Dependency
149
149
  name: ruby-debug19
150
- requirement: &2168552160 !ruby/object:Gem::Requirement
150
+ requirement: &70225077267740 !ruby/object:Gem::Requirement
151
151
  none: false
152
152
  requirements:
153
153
  - - ! '>='
@@ -155,7 +155,7 @@ dependencies:
155
155
  version: '0'
156
156
  type: :development
157
157
  prerelease: false
158
- version_requirements: *2168552160
158
+ version_requirements: *70225077267740
159
159
  description: ''
160
160
  email:
161
161
  - andrew.vos@gmail.com
@@ -243,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
243
243
  version: '0'
244
244
  requirements: []
245
245
  rubyforge_project: spassky
246
- rubygems_version: 1.8.6
246
+ rubygems_version: 1.8.10
247
247
  signing_key:
248
248
  specification_version: 3
249
249
  summary: ''