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 +35 -15
- data/features/list_devices.feature +3 -1
- data/features/run_html_tests.feature +9 -0
- data/features/run_qunit_tests.feature +35 -0
- data/lib/spassky/client/cli.rb +2 -1
- data/lib/spassky/client/directory_reader.rb +24 -7
- data/lib/spassky/client/pusher.rb +12 -9
- data/lib/spassky/client/test_runner.rb +9 -10
- data/lib/spassky/server/app.rb +25 -20
- data/lib/spassky/server/device_list.rb +4 -4
- data/lib/spassky/server/test_run.rb +14 -14
- data/lib/spassky/test_result.rb +5 -5
- data/lib/spassky/version.rb +1 -1
- data/spec/spassky/client/directory_reader_spec.rb +49 -11
- data/spec/spassky/client/test_runner_spec.rb +2 -2
- data/spec/spassky/server/app_spec.rb +17 -5
- data/spec/spassky/server/test_run_spec.rb +19 -19
- data/spec/spassky/test_result_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -1
- metadata +29 -29
data/README.md
CHANGED
@@ -1,53 +1,73 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
7
|
+
Installation
|
8
|
+
------------
|
8
9
|
|
9
10
|
```
|
10
11
|
gem install spassky
|
11
12
|
```
|
12
13
|
|
13
|
-
|
14
|
+
Usage
|
15
|
+
-----
|
14
16
|
|
15
17
|
Start the server:
|
16
18
|
|
17
19
|
```
|
18
|
-
spassky
|
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
|
-
|
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
|
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
|
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
|
46
|
+
spassky run test_directory http://localhost:9191
|
42
47
|
```
|
43
48
|
|
44
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
data/lib/spassky/client/cli.rb
CHANGED
@@ -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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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(
|
26
|
-
write_in_progress_status
|
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
|
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.
|
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
|
|
data/lib/spassky/server/app.rb
CHANGED
@@ -24,15 +24,13 @@ module Spassky::Server
|
|
24
24
|
redirect idle_url
|
25
25
|
end
|
26
26
|
|
27
|
-
def get_device_identifier
|
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
|
-
|
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
|
-
|
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]).
|
65
|
-
:
|
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
|
71
|
-
|
72
|
-
|
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
|
-
|
89
|
-
|
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
|
8
|
-
@devices_and_time_last_connected[
|
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 |
|
13
|
-
Time.now.to_f - @devices_and_time_last_connected[
|
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
|
-
@
|
10
|
+
@status_by_device_id = {}
|
11
11
|
(options[:devices] || []).each do |device|
|
12
|
-
@
|
12
|
+
@status_by_device_id[device] = "in progress"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
@
|
16
|
+
def run_by_device_id?(device_id)
|
17
|
+
@status_by_device_id[device_id] != "in progress"
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
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
|
-
@
|
24
|
+
@status_by_device_id[options[:device_identifier]] = options[:status]
|
25
25
|
end
|
26
26
|
|
27
|
-
def update_connected_devices(
|
28
|
-
@
|
29
|
-
if !
|
30
|
-
@
|
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(@
|
37
|
-
Spassky::DeviceTestStatus.new(
|
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.
|
53
|
-
test_runs.find { |test_run| test_run.
|
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
|
data/lib/spassky/test_result.rb
CHANGED
@@ -34,7 +34,7 @@ module Spassky
|
|
34
34
|
:status => "pass",
|
35
35
|
:device_statuses => @device_statuses.map do |status|
|
36
36
|
{
|
37
|
-
:
|
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["
|
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 :
|
74
|
+
attr_reader :device_id, :status, :test_name
|
75
75
|
|
76
|
-
def initialize(
|
77
|
-
@
|
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
|
data/lib/spassky/version.rb
CHANGED
@@ -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
|
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
|
-
|
17
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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", :
|
128
|
-
mock(:status_one, :status => "fail", :
|
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!(:
|
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!(:
|
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(:
|
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/
|
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.
|
32
|
-
TestRun.
|
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.
|
38
|
-
TestRun.
|
39
|
-
created.
|
40
|
-
TestRun.
|
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.
|
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.
|
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.
|
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.
|
92
|
-
run.
|
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.
|
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
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.
|
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-
|
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: &
|
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: *
|
26
|
+
version_requirements: *70225077309240
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: json
|
29
|
-
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: *
|
37
|
+
version_requirements: *70225077308820
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
39
|
name: sinatra
|
40
|
-
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: *
|
48
|
+
version_requirements: *70225077308400
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
50
|
name: rainbow
|
51
|
-
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: *
|
59
|
+
version_requirements: *70225077307920
|
60
60
|
- !ruby/object:Gem::Dependency
|
61
61
|
name: wurfl-lite
|
62
|
-
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: *
|
70
|
+
version_requirements: *70225077307440
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
72
|
name: commandable
|
73
|
-
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: *
|
81
|
+
version_requirements: *70225077271520
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
83
|
name: rake
|
84
|
-
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: *
|
92
|
+
version_requirements: *70225077270840
|
93
93
|
- !ruby/object:Gem::Dependency
|
94
94
|
name: rspec
|
95
|
-
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: *
|
103
|
+
version_requirements: *70225077270240
|
104
104
|
- !ruby/object:Gem::Dependency
|
105
105
|
name: cucumber
|
106
|
-
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: *
|
114
|
+
version_requirements: *70225077269620
|
115
115
|
- !ruby/object:Gem::Dependency
|
116
116
|
name: capybara
|
117
|
-
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: *
|
125
|
+
version_requirements: *70225077269000
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: aruba
|
128
|
-
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: *
|
136
|
+
version_requirements: *70225077268580
|
137
137
|
- !ruby/object:Gem::Dependency
|
138
138
|
name: fakefs
|
139
|
-
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: *
|
147
|
+
version_requirements: *70225077268160
|
148
148
|
- !ruby/object:Gem::Dependency
|
149
149
|
name: ruby-debug19
|
150
|
-
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: *
|
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.
|
246
|
+
rubygems_version: 1.8.10
|
247
247
|
signing_key:
|
248
248
|
specification_version: 3
|
249
249
|
summary: ''
|