parallel_calabash 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +83 -5
- data/bin/parallel_calabash +38 -8
- data/lib/parallel_calabash.rb +44 -16
- data/lib/parallel_calabash/adb_helper.rb +104 -24
- data/lib/parallel_calabash/feature_grouper.rb +25 -4
- data/lib/parallel_calabash/result_formatter.rb +1 -1
- data/lib/parallel_calabash/runner.rb +209 -37
- data/lib/parallel_calabash/version.rb +1 -1
- data/misc/autostart_test_users.app/Contents/Info.plist +52 -0
- data/misc/autostart_test_users.app/Contents/MacOS/applet +0 -0
- data/misc/autostart_test_users.app/Contents/PkgInfo +1 -0
- data/misc/autostart_test_users.app/Contents/Resources/Scripts/main.scpt +0 -0
- data/misc/autostart_test_users.app/Contents/Resources/applet.icns +0 -0
- data/misc/autostart_test_users.app/Contents/Resources/applet.rsrc +0 -0
- data/misc/autostart_test_users.app/Contents/Resources/description.rtfd/TXT.rtf +4 -0
- data/misc/example_ios_config +24 -0
- data/misc/sim_control.rb +70 -0
- data/spec/lib/parallel_calabash/adb_helper_spec.rb +89 -10
- data/spec/lib/parallel_calabash/runner_spec.rb +13 -8
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d3c746855a83738b07bf9cf32ec7b325a4f395b
|
4
|
+
data.tar.gz: deb3562fc74e7b1e3d013eb98fa0b8148d93c15b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec49bae0ecd21aa231814738b08cb3429657169cedbb0671bcdb9c6d635dbe2f7cedb2f830651abaf1499c3c9a54e91bb83975a51c01d9a4f6b46f2932fa6992
|
7
|
+
data.tar.gz: e5fdee0cc4cfe40d18a82cc719c54ab58d021f371e24135604ad1de5b21dd9dac146f67555b0a1ef5f343acf6350e9e5e91d6d3e446036770e8aa1e281d025ac
|
data/README.md
CHANGED
@@ -5,9 +5,10 @@
|
|
5
5
|
https://www.youtube.com/watch?v=sK3s0txeJvc
|
6
6
|
|
7
7
|
|
8
|
-
Run calabash-android tests in parallel on multiple connected devices. This is inspired by parallel_tests https://rubygems.org/gems/parallel_tests
|
8
|
+
Run calabash-android or calabash-ios tests in parallel on multiple connected devices. This is inspired by parallel_tests https://rubygems.org/gems/parallel_tests
|
9
9
|
|
10
|
-
eg. bundle exec parallel_calabash
|
10
|
+
eg. bundle exec parallel_calabash --apk my.apk -o'--format pretty' features/ --serialize-stdout
|
11
|
+
eg. bundle exec parallel_calabash --app my.app -o'--format pretty' features/ --serialize-stdout
|
11
12
|
|
12
13
|
## Installation
|
13
14
|
|
@@ -25,13 +26,12 @@ Or install it yourself as:
|
|
25
26
|
|
26
27
|
$ gem install parallel_calabash
|
27
28
|
|
28
|
-
## Usage
|
29
|
+
## Usage Android
|
29
30
|
|
30
31
|
Usage: parallel_calabash [options]
|
31
32
|
|
32
33
|
Example: parallel_calabash -a my.apk -o 'cucumber_opts_like_tags_profile_etc_here' features/
|
33
34
|
|
34
|
-
|
35
35
|
-h, --help Show this message
|
36
36
|
-v, --version Show version
|
37
37
|
-a, --apk apk_path apk file path
|
@@ -41,6 +41,84 @@ Example: parallel_calabash -a my.apk -o 'cucumber_opts_like_tags_profile_etc_her
|
|
41
41
|
--group-by-scenarios Distribute equally as per scenarios. This uses cucumber dry run
|
42
42
|
--concurrent Run tests concurrently. Each test will run once on each device.
|
43
43
|
|
44
|
+
## Usage iOS
|
45
|
+
|
46
|
+
Example: parallel_calabash -app my.app --ios_config ~/.parallel_calabash.iphoneos -o '-cucumber -opts' -r '-cucumber -reports>' features/
|
47
|
+
|
48
|
+
-h, --help Show this message
|
49
|
+
-v, --version Show version
|
50
|
+
--app app_path app file path
|
51
|
+
--device_target target ios target if no .parallel-calabash config
|
52
|
+
--device_endpoint endpoint ios endpoint if no .parallel-calabash config
|
53
|
+
--simulator type for simctl create, e.g. 'com.apple.CoreSimulator.SimDeviceType.iPhone-6 com.apple.CoreSimulator.SimRuntime.iOS-8-4'
|
54
|
+
--ios_config file for ios, configuration for devices and users
|
55
|
+
-d, --distribution-tag tag divide features into groups as per occurrence of given tag
|
56
|
+
-f, --filter filter Filter devices to run tests against keys or values in config. Multiple filters seperated by ','
|
57
|
+
--skip_ios_ping_check Skip the connectivity test for iOS devices
|
58
|
+
-o, --cucumber_opts '[OPTIONS]' execute with those cucumber options
|
59
|
+
-r '[REPORTS]', generate these cucumber reports (not during filtering)
|
60
|
+
--cucumber_reports
|
61
|
+
--serialize-stdout Serialize stdout output, nothing will be written until everything is done
|
62
|
+
--concurrent Run tests concurrently. Each test will run once on each device
|
63
|
+
--group-by-scenarios Distribute equally as per scenarios. This uses cucumber dry run
|
64
|
+
|
65
|
+
### iOS set-up
|
66
|
+
|
67
|
+
* iOS testing is only supported on MacOS hosts.
|
68
|
+
* Create as many (Administrator-privileged!) test accounts as you have devices or want simulators (Settings > Users & Groups)
|
69
|
+
* As the main user, the one that runs parallel_calabash, create ~/.parallel_calabash.iphonesimulator and/or ~/.parallel_calabash.iphoneos
|
70
|
+
|
71
|
+
As follows:
|
72
|
+
|
73
|
+
{
|
74
|
+
USERS: [ 'tester1', 'tester2', 'tester3' ],
|
75
|
+
INIT: '[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"',
|
76
|
+
# You only need to specify the port if the default clashes for you. Simulators start sequentially from this.
|
77
|
+
# CALABASH_SERVER_PORT: 3800,
|
78
|
+
# You only need to give the test users' password if you want to run autostart_test_users
|
79
|
+
# PASSWORD: 'testuserspassword',
|
80
|
+
# You only need to set this if you want to run autostart_test_users and the default 6900 clashes with something.
|
81
|
+
# VNC_FORWARD: 6900,
|
82
|
+
# Omit 'DEVICES' entirely if you're only testing on simulators.
|
83
|
+
DEVICES: [
|
84
|
+
{
|
85
|
+
NAME: 'ios-iphone5c-tinkywinkie (8.4.1)',
|
86
|
+
DEVICE_TARGET: '23984729837401987239874987239',
|
87
|
+
DEVICE_ENDPOINT: 'http://192.168.126.206:37265'
|
88
|
+
},
|
89
|
+
{
|
90
|
+
NAME: 'ios-iphone6plus-lala (8.4)',
|
91
|
+
DEVICE_TARGET: 'c987234987983458729375923485792345',
|
92
|
+
DEVICE_ENDPOINT: 'http://192.168.126.205:37265',
|
93
|
+
},
|
94
|
+
{
|
95
|
+
NAME: 'ios-iphone6plus-dipsy (8.4.1)',
|
96
|
+
DEVICE_TARGET: '98723498792873459872398475982347589',
|
97
|
+
DEVICE_ENDPOINT: 'http://192.168.126.207:37265',
|
98
|
+
}
|
99
|
+
]
|
100
|
+
}
|
101
|
+
|
102
|
+
* As the main account, run ssh-keygen
|
103
|
+
* As each test account:
|
104
|
+
1. Use Screen Sharing to log in to the user's desktop (particularly if you're using simulators) to let the computer set it up.
|
105
|
+
2. Settings > Sharing > Remote Login > Allow access for main account (if not already permitted by Remote Management)
|
106
|
+
3. Copy ~main_account/.ssh/id_rsa.pub into each test account's ~tester1/.ssh/authorized_keys
|
107
|
+
4. Any other set-up, e.g. ln -s /Users/main_account/.rvm ~/.rvm
|
108
|
+
|
109
|
+
* If you don't want to test on simulators, your set-up stops here.
|
110
|
+
* If you want to test on simulators too...
|
111
|
+
* ... for each test user, Settings > Sharing > Screen sharing > Allow access (if not already permitted by Remote Management)
|
112
|
+
* ... (we were suprised that a mac mini can cheerfully run upwards of 7 simulators without much struggle)
|
113
|
+
* ... and as your primary user:
|
114
|
+
1. Run: sudo defaults write com.apple.ScreenSharing skipLocalAddressCheck -boolean YES
|
115
|
+
2. Run: ln -s ~/.parallel_config.iphonesimulator ~/.parallel_config.autostart (or whatever your simulators' config is called).
|
116
|
+
3. Add a PASSWORD: 'whatever', in your config - same password for all test users.
|
117
|
+
4. Copy misc/autostart_test_users.app from the Git repository into the system /Applications/ directory
|
118
|
+
5. Run /Applications/autostart_test_users, skip the countdown, and see it complain about accessibility; close the connection request dialog
|
119
|
+
6. In Settings > Privacy & Security > Privacy > Accessibility, allow it - close Settings
|
120
|
+
7. Re-run it, skip the countdown, and it should open a screen sharing session for each test user.
|
121
|
+
8. Add it into Settings > User & Groups > Login Items, set BOOT_DELAY if you need to tune the post-login startup time.
|
44
122
|
|
45
123
|
## FILTERING
|
46
124
|
Filters are partial matches on the device id, or model name.
|
@@ -53,7 +131,7 @@ emulator-5554 device product:sdk_phone_x86_64 model:Android_SDK_built_f
|
|
53
131
|
To run against just the emulator: -f emulator
|
54
132
|
To run against a device id list: -f 4100142545f271b5,4366432135f271c6
|
55
133
|
|
56
|
-
##
|
134
|
+
## REPORTING
|
57
135
|
|
58
136
|
use ENV['TEST_PROCESS_NUMBER'] environment variable in your ruby scripts to find out the process number. you can use this for reporting purpose OR process specific action.
|
59
137
|
|
data/bin/parallel_calabash
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
4
|
-
require
|
4
|
+
require 'parallel_calabash'
|
5
5
|
require 'parallel_calabash/version'
|
6
6
|
require 'optparse'
|
7
7
|
|
@@ -9,7 +9,7 @@ def parse_arguments(arguments)
|
|
9
9
|
options = {}
|
10
10
|
opt_parser = OptionParser.new do |opts|
|
11
11
|
opts.banner = "Usage: parallel_calabash [options]\n" \
|
12
|
-
"Example: parallel_calabash -a my.apk -o '<cucumber opts>' features/"
|
12
|
+
"Example: parallel_calabash -a my.apk -o '<cucumber opts>' -r '<cucumber_reports>' features/"
|
13
13
|
|
14
14
|
opts.on("-h", "--help", "Show this message") do
|
15
15
|
puts opts
|
@@ -25,18 +25,46 @@ def parse_arguments(arguments)
|
|
25
25
|
options[:apk_path] = apk_path
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
opts.on('--app app_path', 'app file path') do |app_path|
|
29
|
+
options[:app_path] = app_path
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('--device_target target', 'ios target if no .parallel-calabash config') do |device_target|
|
33
|
+
options[:device_target] = device_target
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('--device_endpoint endpoint', 'ios endpoint if no .parallel-calabash config') do |device_endpoint|
|
37
|
+
options[:device_endpoint] = device_endpoint
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('--simulator type', "for simctl create, e.g. 'com.apple.CoreSimulator.SimDeviceType.iPhone-6 com.apple.CoreSimulator.SimRuntime.iOS-8-4'") do |simulator|
|
41
|
+
options[:simulator] = simulator
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('--ios_config file', 'for ios, configuration for devices and users') do |ios_config|
|
45
|
+
options[:ios_config] = ios_config
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-d", "--distribution-tag tag", "divide features into groups as per occurrence of given tag") do |distribution_tag|
|
29
49
|
options[:distribution_tag] = distribution_tag
|
30
50
|
end
|
31
51
|
|
32
52
|
opts.on("-f", "--filter filter", "Filter devices to run tests against using partial device id or model name matching. Multiple filters seperated by ','") do |filter_opts|
|
33
|
-
options[:filter] = filter_opts.split(
|
53
|
+
options[:filter] = filter_opts.split(',').grep(/./)
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on('--skip_ios_ping_check', 'Skip the connectivity test for iOS devices') do
|
57
|
+
options[:skip_ios_ping_check] = true
|
34
58
|
end
|
35
59
|
|
36
60
|
opts.on("-o", "--cucumber_opts '[OPTIONS]'", "execute with those cucumber options") do |cucumber_opts|
|
37
61
|
options[:cucumber_options] = cucumber_opts
|
38
62
|
end
|
39
63
|
|
64
|
+
opts.on("-r", "--cucumber_reports '[REPORTS]'", "generate these cucumber reports (not during filtering)") do |cucumber_reports|
|
65
|
+
options[:cucumber_reports] = cucumber_reports
|
66
|
+
end
|
67
|
+
|
40
68
|
opts.on("--serialize-stdout", "Serialize stdout output, nothing will be written until everything is done") do |mute_output|
|
41
69
|
options[:mute_output] = mute_output
|
42
70
|
end
|
@@ -53,14 +81,16 @@ def parse_arguments(arguments)
|
|
53
81
|
|
54
82
|
opt_parser.parse!(arguments)
|
55
83
|
options[:feature_folder] = arguments
|
84
|
+
fail '-a apk and -p app are mutually exclusive' if options.has_key?(:apk_path) && options.has_key?(:app_path)
|
85
|
+
fail '-a apk or -p app is required' unless options.has_key?(:apk_path) || options.has_key?(:app_path)
|
56
86
|
options
|
57
|
-
rescue OptionParser::InvalidOption
|
58
|
-
puts "Invalid arguments "
|
59
|
-
|
87
|
+
rescue OptionParser::InvalidOption => e
|
88
|
+
puts "Invalid arguments #{e}"
|
89
|
+
fail opt_parser.help
|
60
90
|
end
|
61
91
|
|
62
92
|
options = parse_arguments(ARGV)
|
63
93
|
|
64
|
-
ParallelCalabash.run_tests_in_parallel
|
94
|
+
ParallelCalabash::ParallelCalabashApp.new(options).run_tests_in_parallel
|
65
95
|
|
66
96
|
|
data/lib/parallel_calabash.rb
CHANGED
@@ -8,33 +8,62 @@ require 'parallel_calabash/result_formatter'
|
|
8
8
|
require 'rbconfig'
|
9
9
|
|
10
10
|
module ParallelCalabash
|
11
|
-
|
12
11
|
WINDOWS = (RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/)
|
13
|
-
class << self
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
class ParallelCalabashApp
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
@options = options
|
17
|
+
@helper = if options.has_key?(:apk_path)
|
18
|
+
ParallelCalabash::AdbHelper.new(options[:filter])
|
19
|
+
else
|
20
|
+
ParallelCalabash::IosHelper.new(
|
21
|
+
options[:filter],
|
22
|
+
{
|
23
|
+
DEVICE_TARGET: options[:device_target],
|
24
|
+
DEVICE_ENDPOINT: options[:device_endpoint],
|
25
|
+
},
|
26
|
+
options[:ios_config]
|
27
|
+
)
|
28
|
+
end
|
29
|
+
@runner = if options.has_key?(:apk_path)
|
30
|
+
ParallelCalabash::AndroidRunner.new(@helper, options[:mute_output])
|
31
|
+
else
|
32
|
+
ParallelCalabash::IosRunner.new(@helper, options[:mute_output], options[:skip_ios_ping_check])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def number_of_processes_to_start
|
38
|
+
number_of_processes = @helper.number_of_connected_devices
|
17
39
|
raise "\n**** NO DEVICE FOUND ****\n" if number_of_processes==0
|
18
|
-
puts
|
19
|
-
puts " #{number_of_processes} DEVICES FOUND"
|
20
|
-
puts
|
40
|
+
puts '*******************************'
|
41
|
+
puts " #{number_of_processes} DEVICES FOUND:"
|
42
|
+
puts @helper.connected_devices_with_model_info
|
43
|
+
puts '*******************************'
|
21
44
|
number_of_processes
|
22
45
|
end
|
23
46
|
|
24
|
-
def run_tests_in_parallel
|
25
|
-
|
26
|
-
|
47
|
+
def run_tests_in_parallel
|
48
|
+
@runner.prepare_for_parallel_execution
|
49
|
+
number_of_processes = number_of_processes_to_start
|
27
50
|
test_results = nil
|
28
51
|
report_time_taken do
|
29
|
-
groups = FeatureGrouper.feature_groups(options, number_of_processes)
|
52
|
+
groups = FeatureGrouper.feature_groups(@options, number_of_processes)
|
30
53
|
threads = groups.size
|
31
|
-
|
32
|
-
|
33
|
-
|
54
|
+
puts "Running with #{threads} threads: #{groups}"
|
55
|
+
complete = []
|
56
|
+
test_results = Parallel.map_with_index(
|
57
|
+
groups,
|
58
|
+
:in_threads => threads,
|
59
|
+
:finish => lambda { |_, i, _| complete.push(i); print complete, "\n" }) do |group, index|
|
60
|
+
@runner.run_tests(group, index, @options)
|
34
61
|
end
|
62
|
+
puts 'All threads complete'
|
35
63
|
ResultFormatter.report_results(test_results)
|
36
64
|
end
|
37
|
-
|
65
|
+
@runner.prepare_for_parallel_execution
|
66
|
+
puts 'Parallel run complete'
|
38
67
|
Kernel.exit(1) if any_test_failed?(test_results)
|
39
68
|
end
|
40
69
|
|
@@ -49,6 +78,5 @@ module ParallelCalabash
|
|
49
78
|
mm, ss = time_in_sec.divmod(60)
|
50
79
|
puts "\nTook #{mm} Minutes, #{ss.round(2)} Seconds"
|
51
80
|
end
|
52
|
-
|
53
81
|
end
|
54
82
|
end
|
@@ -1,43 +1,123 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module ParallelCalabash
|
2
|
-
module
|
3
|
-
|
4
|
+
module DevicesHelper
|
5
|
+
def device_for_process process_num
|
6
|
+
connected_devices_with_model_info[process_num]
|
7
|
+
end
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
9
|
+
def number_of_connected_devices
|
10
|
+
connected_devices_with_model_info.size
|
11
|
+
end
|
12
|
+
end
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
14
|
+
class AdbHelper
|
15
|
+
include ParallelCalabash::DevicesHelper
|
16
|
+
|
17
|
+
def initialize(filter = [])
|
18
|
+
@filter = filter
|
19
|
+
end
|
12
20
|
|
13
|
-
|
14
|
-
|
15
|
-
|
21
|
+
def connected_devices_with_model_info
|
22
|
+
begin
|
23
|
+
list =
|
16
24
|
`adb devices -l`.split("\n").collect do |line|
|
17
25
|
device = device_id_and_model(line)
|
18
|
-
filter_device(device
|
26
|
+
filter_device(device)
|
19
27
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
28
|
+
list.compact
|
29
|
+
rescue
|
30
|
+
[]
|
24
31
|
end
|
32
|
+
end
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
34
|
+
def device_id_and_model line
|
35
|
+
if line.match(/device(?!s)/)
|
36
|
+
[line.split(" ").first, line.scan(/model:(.*) device/).flatten.first]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def filter_device device
|
41
|
+
if @filter && !@filter.empty? && device
|
42
|
+
device unless @filter.collect { |f| device[0].match(f) || device[1].match(f) }.compact.empty?
|
43
|
+
else
|
44
|
+
device
|
30
45
|
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class IosHelper
|
50
|
+
include ParallelCalabash::DevicesHelper
|
31
51
|
|
32
|
-
|
33
|
-
|
34
|
-
|
52
|
+
def initialize(filter = nil, default_simulator = nil, config_file = nil, instruments = nil)
|
53
|
+
@filter = filter || []
|
54
|
+
@default_simulator = default_simulator || {}
|
55
|
+
config_file = config_file || "#{ENV['HOME']}/.parallel_calabash"
|
56
|
+
if config_file.is_a? Hash
|
57
|
+
@config = config_file
|
35
58
|
else
|
36
|
-
|
59
|
+
@config = File.exist?(config_file) ? eval(File.read(config_file)) : {}
|
60
|
+
end
|
61
|
+
@instruments = instruments || %x(instruments -s devices ; echo) # Bizarre workaround for xcode 7
|
62
|
+
end
|
63
|
+
|
64
|
+
def xcode7?
|
65
|
+
!@instruments.match(' Simulator\)')
|
66
|
+
end
|
67
|
+
|
68
|
+
def connected_devices_with_model_info
|
69
|
+
return @devices if @devices
|
70
|
+
if @config[:DEVICES]
|
71
|
+
configs = apply_filter(compute_devices)
|
72
|
+
fail '** No devices (or users) unfiltered!' if configs.empty?
|
73
|
+
else
|
74
|
+
configs = apply_filter(compute_simulators)
|
75
|
+
configs = configs.empty? ? [@default_simulator] : configs
|
76
|
+
end
|
77
|
+
@devices = configs
|
78
|
+
end
|
79
|
+
|
80
|
+
def compute_simulators
|
81
|
+
port = (@config[:CALABASH_SERVER_PORT] || 28000).to_i
|
82
|
+
users = @config[:USERS] || []
|
83
|
+
init = @config[:INIT] || ''
|
84
|
+
users.map.with_index do |u, i|
|
85
|
+
{USER: u, CALABASH_SERVER_PORT: port + i, INIT: init}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def compute_devices
|
90
|
+
users = @config[:USERS] || []
|
91
|
+
init = @config[:INIT] || ''
|
92
|
+
devices = remove_unconnected_devices(@config[:DEVICES])
|
93
|
+
fail 'Devices configured, but no devices attached!' if devices.empty?
|
94
|
+
configs = devices.map.with_index do |d, i|
|
95
|
+
if users[i]
|
96
|
+
d[:USER] = users[i]
|
97
|
+
d[:INIT] = init
|
98
|
+
d
|
99
|
+
else
|
100
|
+
print "** No user for device #{d}"
|
101
|
+
nil
|
37
102
|
end
|
38
103
|
end
|
104
|
+
configs.compact
|
105
|
+
end
|
39
106
|
|
107
|
+
def apply_filter(configs)
|
108
|
+
return configs if @filter.empty?
|
109
|
+
filter_join = @filter.join('|')
|
110
|
+
configs.select do |c|
|
111
|
+
[c.keys, c.values].flatten.find { |k| k.to_s.match(filter_join) }
|
112
|
+
end
|
40
113
|
end
|
41
114
|
|
115
|
+
def remove_unconnected_devices(configs)
|
116
|
+
udids = @instruments.each_line.map { |n| n.match(/\[(.*)\]/) && $1 }.flatten.compact
|
117
|
+
configs.find_all do |c|
|
118
|
+
var = c[:DEVICE_TARGET]
|
119
|
+
!udids.grep(var).empty?
|
120
|
+
end
|
121
|
+
end
|
42
122
|
end
|
43
123
|
end
|
@@ -11,6 +11,22 @@ module ParallelCalabash
|
|
11
11
|
feature_groups_by_feature_files(options[:feature_folder], group_size)
|
12
12
|
end
|
13
13
|
|
14
|
+
def feature_groups_by_scenarios(features_scenarios,group_size)
|
15
|
+
puts "Scenarios: #{features_scenarios.size}"
|
16
|
+
min_number_scenarios_per_group = features_scenarios.size/group_size
|
17
|
+
remaining_number_of_scenarios = features_scenarios.size % group_size
|
18
|
+
groups = Array.new(group_size) { [] }
|
19
|
+
groups.each do |group|
|
20
|
+
min_number_scenarios_per_group.times { group << features_scenarios.delete_at(0) }
|
21
|
+
end
|
22
|
+
unless remaining_number_of_scenarios==0
|
23
|
+
groups[0..(remaining_number_of_scenarios-1)].each do |group|
|
24
|
+
group << features_scenarios.delete_at(0)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
groups.reject(&:empty?)
|
28
|
+
end
|
29
|
+
|
14
30
|
def concurrent_feature_groups(feature_folder, number_of_groups)
|
15
31
|
groups = []
|
16
32
|
(0...number_of_groups).each{ groups << feature_files_in_folder(feature_folder) }
|
@@ -42,15 +58,20 @@ module ParallelCalabash
|
|
42
58
|
generate_dry_run_report options
|
43
59
|
raise "Can not create dry run for scenario distribution" unless File.exists?("parallel_calabash_dry_run.json")
|
44
60
|
distribution_data = JSON.parse(File.read("parallel_calabash_dry_run.json"))
|
61
|
+
# puts "SCENARIO GROUPS #{distribution_data}"
|
45
62
|
all_runnable_scenarios = distribution_data.map do |feature|
|
46
63
|
unless feature["elements"].nil?
|
47
64
|
feature["elements"].map do |scenario|
|
48
65
|
if scenario["keyword"] == 'Scenario'
|
49
66
|
"#{feature["uri"]}:#{scenario["line"]}"
|
50
67
|
elsif scenario['keyword'] == 'Scenario Outline'
|
51
|
-
scenario["examples"]
|
52
|
-
|
53
|
-
|
68
|
+
if scenario["examples"]
|
69
|
+
scenario["examples"].map { |example|
|
70
|
+
"#{feature["uri"]}:#{example["line"]}"
|
71
|
+
}
|
72
|
+
else
|
73
|
+
"#{feature["uri"]}:#{scenario["line"]}" # Cope with --expand
|
74
|
+
end
|
54
75
|
end
|
55
76
|
end
|
56
77
|
end
|
@@ -59,7 +80,7 @@ module ParallelCalabash
|
|
59
80
|
end
|
60
81
|
|
61
82
|
def generate_dry_run_report options
|
62
|
-
|
83
|
+
%x( cucumber #{options[:cucumber_options]} --dry-run -f json --out parallel_calabash_dry_run.json #{options[:feature_folder].join(' ')} )
|
63
84
|
end
|
64
85
|
|
65
86
|
def feature_files_in_folder(feature_dir)
|
@@ -1,52 +1,224 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'find'
|
3
|
+
|
1
4
|
module ParallelCalabash
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
module Runner
|
6
|
+
def execute_command_for_process(process_number, cmd)
|
7
|
+
output = open("|#{cmd}", 'r') { |output| show_output(output, process_number) }
|
8
|
+
exitstatus = $?.exitstatus
|
9
|
+
|
10
|
+
if @silence
|
11
|
+
$stdout.print output
|
12
|
+
$stdout.flush
|
13
|
+
end
|
14
|
+
puts "\n****** PROCESS #{process_number} COMPLETED ******\n\n"
|
15
|
+
{:stdout => output, :exit_status => exitstatus}
|
16
|
+
end
|
17
|
+
|
18
|
+
def show_output(output, process_number)
|
19
|
+
result = ''
|
20
|
+
loop do
|
21
|
+
begin
|
22
|
+
unless @silence
|
23
|
+
read = output.readline()
|
24
|
+
$stdout.print "#{process_number}> #{read}"
|
25
|
+
$stdout.flush
|
26
|
+
else
|
27
|
+
read = output.readpartial(1000000) # read whatever chunk we can get
|
28
|
+
end
|
29
|
+
result << read
|
30
|
+
end
|
31
|
+
end rescue EOFError
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class AndroidRunner
|
37
|
+
include Runner
|
38
|
+
|
39
|
+
def initialize(device_helper, silence)
|
40
|
+
@device_helper = device_helper
|
41
|
+
@silence = silence
|
42
|
+
end
|
43
|
+
|
44
|
+
def prepare_for_parallel_execution
|
45
|
+
# Android is fairly sane....
|
46
|
+
end
|
47
|
+
|
48
|
+
def base_command
|
49
|
+
'calabash-android run'
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_tests(test_files, process_number, options)
|
53
|
+
cmd = command_for_test(
|
54
|
+
process_number, base_command, options[:apk_path],
|
55
|
+
"#{options[:cucumber_options]} #{options[:cucumber_reports]}", test_files)
|
56
|
+
$stdout.print "#{process_number}>> Command: #{cmd}\n"
|
57
|
+
$stdout.flush
|
58
|
+
execute_command_for_process(process_number, cmd)
|
59
|
+
end
|
60
|
+
|
61
|
+
def command_for_test(process_number, base_command, apk_path, cucumber_options, test_files)
|
62
|
+
cmd = [base_command, apk_path, cucumber_options, *test_files].compact*' '
|
63
|
+
device_id, device_info = @device_helper.device_for_process process_number
|
64
|
+
env = {
|
65
|
+
AUTOTEST: '1',
|
66
|
+
ADB_DEVICE_ARG: device_id,
|
67
|
+
DEVICE_INFO: device_info,
|
68
|
+
TEST_PROCESS_NUMBER: (process_number+1).to_s,
|
69
|
+
SCREENSHOT_PATH: device_id.to_s + '_'
|
70
|
+
}
|
71
|
+
separator = (WINDOWS ? ' & ' : ';')
|
72
|
+
exports = env.map { |k, v| WINDOWS ? "(SET \"#{k}=#{v}\")" : "#{k}=#{v};export #{k}" }.join(separator)
|
73
|
+
exports + separator + cmd
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class IosRunner
|
78
|
+
include Runner
|
79
|
+
|
80
|
+
def initialize(device_helper, silence, skip_ios_ping_check)
|
81
|
+
@device_helper = device_helper
|
82
|
+
@silence = silence
|
83
|
+
@skip_ios_ping_check = skip_ios_ping_check
|
84
|
+
end
|
85
|
+
|
86
|
+
def base_command
|
87
|
+
'bundle exec cucumber'
|
88
|
+
end
|
89
|
+
|
90
|
+
def run_tests(test_files, process_number, options)
|
91
|
+
test = command_for_test(
|
92
|
+
process_number, test_files,
|
93
|
+
options[:app_path], "#{options[:cucumber_options]} #{options[:cucumber_reports]}",
|
94
|
+
options[:simulator] || '0-0')
|
95
|
+
$stdout.print "#{process_number}>> Command: #{test}\n"
|
96
|
+
$stdout.flush
|
97
|
+
|
98
|
+
o = execute_command_for_process(process_number, test)
|
99
|
+
device = @device_helper.device_for_process process_number
|
100
|
+
log = "/tmp/PCal-#{device[:USER]}.process_number"
|
101
|
+
puts "Writing log #{log}"
|
102
|
+
open(log, 'w') { |file| file.print o[:stdout] }
|
103
|
+
o
|
104
|
+
end
|
105
|
+
|
106
|
+
def command_for_test(process_number, test_files, app_path, cucumber_options, simulator)
|
107
|
+
device = @device_helper.device_for_process process_number
|
108
|
+
separator = (WINDOWS ? ' & ' : ';')
|
109
|
+
remote = device[:USER] ? "ssh #{device[:USER]}@localhost" : 'bash -c'
|
110
|
+
|
111
|
+
if device[:CALABASH_SERVER_PORT]
|
112
|
+
user_app = copy_app_set_port(app_path, device)
|
113
|
+
else
|
114
|
+
user_app = app_path
|
6
115
|
end
|
7
116
|
|
8
|
-
|
9
|
-
|
10
|
-
|
117
|
+
device_name = device[:DEVICE_NAME] || "PCal-#{device[:USER]}"
|
118
|
+
device_simulator = device[:SIMULATOR] || simulator
|
119
|
+
|
120
|
+
# Device target changed in XCode 7, losing ' Simulator' for some reason.
|
121
|
+
maybe_simulator = @device_helper.xcode7? ? '' : ' Simulator'
|
122
|
+
device_target = device[:DEVICE_TARGET] || "#{device_name} (#{version(device_simulator)}#{maybe_simulator})"
|
123
|
+
device_info = device[:DEVICE_TARGET] || (device[:USER] ? create_simulator(device_name, remote, simulator) : '')
|
124
|
+
device_endpoint = device[:DEVICE_ENDPOINT] || "http://localhost:#{device[:CALABASH_SERVER_PORT]}"
|
125
|
+
$stdout.print "#{process_number}>> Device: #{device_info} = #{device_name} = #{device_target}\n"
|
126
|
+
$stdout.flush
|
127
|
+
|
128
|
+
unless @skip_ios_ping_check
|
129
|
+
hostname = device_endpoint.match("http://(.*):").captures.first
|
130
|
+
pingable = system "ping -c 1 -o #{hostname}"
|
131
|
+
fail "Cannot ping device_endpoint host: #{hostname}" unless pingable
|
132
|
+
end
|
133
|
+
|
134
|
+
cmd = [base_command, "APP_BUNDLE_PATH=#{user_app}", cucumber_options, *test_files].compact*' '
|
135
|
+
|
136
|
+
env = {
|
137
|
+
AUTOTEST: '1',
|
138
|
+
DEVICE_ENDPOINT: device_endpoint,
|
139
|
+
DEVICE_TARGET: device_target,
|
140
|
+
DEVICE_INFO: device_info,
|
141
|
+
TEST_USER: device[:USER] || %x( whoami ).strip,
|
142
|
+
# 'DEBUG_UNIX_CALLS' => '1',
|
143
|
+
TEST_PROCESS_NUMBER: (process_number+1).to_s,
|
144
|
+
SCREENSHOT_PATH: "PCal_#{process_number+1}_"
|
145
|
+
}
|
146
|
+
env['BUNDLE_ID'] = ENV['BUNDLE_ID'] if ENV['BUNDLE_ID']
|
147
|
+
exports = env.map { |k, v| WINDOWS ? "(SET \"#{k}=#{v}\")" : "#{k}='#{v}';export #{k}" }.join(separator)
|
148
|
+
|
149
|
+
cmd = [ exports, "#{device[:INIT] || ' : '}", "cd #{File.absolute_path('.')}", "umask 002", cmd].join(separator)
|
150
|
+
|
151
|
+
if device[:USER]
|
152
|
+
"#{remote} bash -lc \"#{cmd}\" 2>&1"
|
153
|
+
else
|
154
|
+
"bash -c \"#{cmd}\" 2>&1"
|
11
155
|
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# def udid(name)
|
159
|
+
# name = name.gsub(/(\W)/, '\\\\\\1')
|
160
|
+
# line = %x( instruments -s devices ).split("\n").grep(/#{name}/)
|
161
|
+
# fail "Found #{line.size} matches for #{name}, expected 1" unless line.size == 1
|
162
|
+
# line.first.match(/\[(\S+)\]/).captures.first.to_s
|
163
|
+
# end
|
12
164
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
exitstatus = $?.exitstatus
|
165
|
+
def version(simulator)
|
166
|
+
simulator.match('\d+-\d+$').to_s.gsub('-', '.')
|
167
|
+
end
|
17
168
|
|
18
|
-
|
19
|
-
|
20
|
-
|
169
|
+
def prepare_for_parallel_execution
|
170
|
+
# copy-chown all the files, and set everything group-writable.
|
171
|
+
Find.find('.') do |path|
|
172
|
+
if File.file?(path) && !File.stat(path).owned?
|
173
|
+
temp = "#{path}......"
|
174
|
+
FileUtils.copy(path, temp)
|
175
|
+
FileUtils.move(temp, path)
|
176
|
+
puts "Chowned/copied.... #{path}"
|
21
177
|
end
|
22
|
-
puts "\n****** PROCESS #{process_number} COMPLETED ******\n\n"
|
23
|
-
{:stdout => output, :exit_status => exitstatus}
|
24
178
|
end
|
179
|
+
FileUtils.chmod_R('g+w', 'build/reports')
|
180
|
+
FileUtils.chmod('g+w', Dir['*'])
|
181
|
+
FileUtils.chmod('g+w', '.')
|
182
|
+
end
|
183
|
+
|
184
|
+
def create_simulator(device_name, ssh, simulator)
|
185
|
+
stop_and_remove(device_name, ssh)
|
186
|
+
puts "Double check..."
|
187
|
+
stop_and_remove(device_name, ssh)
|
188
|
+
puts "OK if none"
|
189
|
+
|
190
|
+
device_info = %x( #{ssh} "xcrun simctl create #{device_name} #{simulator}" ).strip
|
191
|
+
fail "Failed to create #{device_name} for #{ssh}" unless device_info
|
192
|
+
device_info
|
193
|
+
end
|
25
194
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
195
|
+
def stop_and_remove(device_name, ssh)
|
196
|
+
devices = %x( #{ssh} "xcrun simctl list devices" | grep #{device_name} )
|
197
|
+
puts "Devices: #{devices}"
|
198
|
+
devices.each_line do |device|
|
199
|
+
_name, id, state = device.match(/^\s*([^(]*?)\s*\((\S+)\)\s+\((\S+)\)/).captures
|
200
|
+
puts 'Shutdown: ' + %x( #{ssh} "xcrun simctl shutdown #{id}" ) if state =~ /booted/
|
201
|
+
puts 'Delete: ' + %x( #{ssh} "xcrun simctl delete #{id}" )
|
33
202
|
end
|
203
|
+
end
|
34
204
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
result
|
205
|
+
def copy_app_set_port(app_path, device)
|
206
|
+
user_path = File.dirname(app_path) + '/' + device[:USER]
|
207
|
+
FileUtils.rmtree(user_path)
|
208
|
+
FileUtils.mkdir_p(user_path)
|
209
|
+
user_app = user_path + '/' + File.basename(app_path)
|
210
|
+
FileUtils.copy_entry(app_path, user_app)
|
211
|
+
|
212
|
+
# Set plist.
|
213
|
+
|
214
|
+
system("/usr/libexec/PlistBuddy -c 'Delete CalabashServerPort integer #{device[:CALABASH_SERVER_PORT]}' #{user_app}/Info.plist")
|
215
|
+
unless system("/usr/libexec/PlistBuddy -c 'Add CalabashServerPort integer #{device[:CALABASH_SERVER_PORT]}' #{user_app}/Info.plist")
|
216
|
+
raise "Unable to set CalabashServerPort in #{user_app}/Info.plist"
|
48
217
|
end
|
49
218
|
|
219
|
+
puts "User app: #{user_app}"
|
220
|
+
|
221
|
+
user_app
|
50
222
|
end
|
51
223
|
end
|
52
|
-
end
|
224
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>CFBundleAllowMixedLocalizations</key>
|
6
|
+
<true/>
|
7
|
+
<key>CFBundleDevelopmentRegion</key>
|
8
|
+
<string>English</string>
|
9
|
+
<key>CFBundleExecutable</key>
|
10
|
+
<string>applet</string>
|
11
|
+
<key>CFBundleIconFile</key>
|
12
|
+
<string>applet</string>
|
13
|
+
<key>CFBundleIdentifier</key>
|
14
|
+
<string>com.apple.ScriptEditor.id.autostart-test-users</string>
|
15
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
16
|
+
<string>6.0</string>
|
17
|
+
<key>CFBundleName</key>
|
18
|
+
<string>autostart_test_users</string>
|
19
|
+
<key>CFBundlePackageType</key>
|
20
|
+
<string>APPL</string>
|
21
|
+
<key>CFBundleShortVersionString</key>
|
22
|
+
<string>1.0</string>
|
23
|
+
<key>CFBundleSignature</key>
|
24
|
+
<string>aplt</string>
|
25
|
+
<key>LSMinimumSystemVersionByArchitecture</key>
|
26
|
+
<dict>
|
27
|
+
<key>x86_64</key>
|
28
|
+
<string>10.6</string>
|
29
|
+
</dict>
|
30
|
+
<key>LSRequiresCarbon</key>
|
31
|
+
<true/>
|
32
|
+
<key>WindowState</key>
|
33
|
+
<dict>
|
34
|
+
<key>bundleDividerCollapsed</key>
|
35
|
+
<true/>
|
36
|
+
<key>bundlePositionOfDivider</key>
|
37
|
+
<real>0.0</real>
|
38
|
+
<key>dividerCollapsed</key>
|
39
|
+
<false/>
|
40
|
+
<key>eventLogLevel</key>
|
41
|
+
<integer>-1</integer>
|
42
|
+
<key>name</key>
|
43
|
+
<string>ScriptWindowState</string>
|
44
|
+
<key>positionOfDivider</key>
|
45
|
+
<real>662</real>
|
46
|
+
<key>savedFrame</key>
|
47
|
+
<string>191 73 1004 804 0 0 1440 877 </string>
|
48
|
+
<key>selectedTab</key>
|
49
|
+
<string>log</string>
|
50
|
+
</dict>
|
51
|
+
</dict>
|
52
|
+
</plist>
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
APPLaplt
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
USERS: [ 'tester1', 'tester2', 'tester3' ],
|
3
|
+
INIT: '[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"',
|
4
|
+
# You only need to specify the port if the default clashes for you. Simulators start sequentially from this.
|
5
|
+
# CALABASH_SERVER_PORT: 3800,
|
6
|
+
# Omit 'DEVICES' entirely if you're only testing on simulators.
|
7
|
+
DEVICES: [
|
8
|
+
{
|
9
|
+
NAME: 'ios-iphone5c-tinkywinkie (8.4.1)',
|
10
|
+
DEVICE_TARGET: '23984729837401987239874987239',
|
11
|
+
DEVICE_ENDPOINT: 'http://192.168.126.206:37265'
|
12
|
+
},
|
13
|
+
{
|
14
|
+
NAME: 'ios-iphone6plus-lala (8.4)',
|
15
|
+
DEVICE_TARGET: 'c987234987983458729375923485792345',
|
16
|
+
DEVICE_ENDPOINT: 'http://192.168.126.205:37265',
|
17
|
+
},
|
18
|
+
{
|
19
|
+
NAME: 'ios-iphone6plus-dipsy (8.4.1)',
|
20
|
+
DEVICE_TARGET: '98723498792873459872398475982347589',
|
21
|
+
DEVICE_ENDPOINT: 'http://192.168.126.207:37265',
|
22
|
+
}
|
23
|
+
]
|
24
|
+
}
|
data/misc/sim_control.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'run_loop/sim_control'
|
2
|
+
|
3
|
+
# This patch is needed to work around 'simctl erase' not working on a booted device:
|
4
|
+
# Because of RunLoop's assumptions and Mac inadequacies relating to multiple
|
5
|
+
# users, this 'shutdown' helped our 'Before scenario' hooks.
|
6
|
+
#
|
7
|
+
# You'll want to copy this to your Cucumber project. Probably.
|
8
|
+
#
|
9
|
+
# Copyright whoever owns RunLoop, except for the hashed block which is presumably Badoo's but
|
10
|
+
# we'll let you have it because we're nice.
|
11
|
+
|
12
|
+
puts "Monkeypatch: #{__FILE__}"
|
13
|
+
RunLoop::SimControl.class_eval do
|
14
|
+
# @!visibility private
|
15
|
+
# Uses the `simctl erase` command to reset a simulator content and settings.
|
16
|
+
# If no `sim_udid` is nil, _all_ simulators are reset.
|
17
|
+
#
|
18
|
+
# # @note This is an Xcode 6 only method. It will raise an error if called on
|
19
|
+
# Xcode < 6.
|
20
|
+
#
|
21
|
+
# @note This method will quit the simulator.
|
22
|
+
#
|
23
|
+
# @param [String] sim_udid The udid of the simulator that will be reset.
|
24
|
+
# If sim_udid is nil, _all_ simulators will be reset.
|
25
|
+
# @raise [RuntimeError] If called on Xcode < 6.
|
26
|
+
# @raise [RuntimeError] If `sim_udid` is not a valid simulator udid. Valid
|
27
|
+
# simulator udids are determined by calling `simctl list`.
|
28
|
+
def simctl_reset(sim_udid = nil)
|
29
|
+
unless xcode_version_gte_6?
|
30
|
+
fail 'this method is only available on Xcode >= 6'
|
31
|
+
end
|
32
|
+
|
33
|
+
quit_sim
|
34
|
+
|
35
|
+
sim_details = sim_details(:udid)
|
36
|
+
simctl_erase = lambda { |udid|
|
37
|
+
# ################################################
|
38
|
+
puts '** MONKEYPATCH Speculative shutdown - may fail harmlessly'
|
39
|
+
shutdown_output = %x( xcrun simctl shutdown #{udid} )
|
40
|
+
puts "** MONKEYPATCH Injected simctl_shutdown: #{shutdown_output} [#{__FILE__}]"
|
41
|
+
# ################################################
|
42
|
+
args = "simctl erase #{udid}".split(' ')
|
43
|
+
Open3.popen3('xcrun', *args) do |_, stdout, stderr, wait_thr|
|
44
|
+
out = stdout.read.strip
|
45
|
+
err = stderr.read.strip
|
46
|
+
if ENV['DEBUG_UNIX_CALLS'] == '1'
|
47
|
+
cmd = "xcrun simctl erase #{udid}"
|
48
|
+
puts __FILE__ + " sim_erase cmd #{cmd}"
|
49
|
+
puts "#{cmd} => stdout: '#{out}' | stderr: '#{err}'"
|
50
|
+
end
|
51
|
+
wait_thr.value.success?
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
# Call erase on all simulators
|
56
|
+
if sim_udid.nil?
|
57
|
+
res = []
|
58
|
+
sim_details.each_key do |key|
|
59
|
+
res << simctl_erase.call(key)
|
60
|
+
end
|
61
|
+
res.all?
|
62
|
+
else
|
63
|
+
if sim_details[sim_udid]
|
64
|
+
simctl_erase.call(sim_udid)
|
65
|
+
else
|
66
|
+
fail "Could not find simulator with udid '#{sim_udid}'"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,53 +1,132 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
2
|
+
require 'minitest/mock'
|
3
3
|
require 'parallel_calabash/adb_helper'
|
4
4
|
describe ParallelCalabash::AdbHelper do
|
5
5
|
|
6
6
|
describe :device_id_and_model do
|
7
7
|
it 'should not match any devices in list of devices attached line' do
|
8
|
-
expect(ParallelCalabash::AdbHelper.device_id_and_model("List of devices attached")).to eq nil
|
8
|
+
expect(ParallelCalabash::AdbHelper.new([]).device_id_and_model("List of devices attached")).to eq nil
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'should match devices if there is a space after the word device' do
|
12
|
-
expect(ParallelCalabash::AdbHelper.device_id_and_model("emulator-5554 device ")).to eq \
|
12
|
+
expect(ParallelCalabash::AdbHelper.new([]).device_id_and_model("emulator-5554 device ")).to eq \
|
13
13
|
["emulator-5554", nil]
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'should match devices if there is not a space after the word device' do
|
17
|
-
expect(ParallelCalabash::AdbHelper.device_id_and_model("emulator-5554 device")).to eq \
|
17
|
+
expect(ParallelCalabash::AdbHelper.new([]).device_id_and_model("emulator-5554 device")).to eq \
|
18
18
|
["emulator-5554", nil]
|
19
19
|
end
|
20
20
|
|
21
21
|
it 'should not match a device if it is an empty line' do
|
22
|
-
expect(ParallelCalabash::AdbHelper.device_id_and_model("")).to eq nil
|
22
|
+
expect(ParallelCalabash::AdbHelper.new([]).device_id_and_model("")).to eq nil
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'should match physical devices' do
|
26
26
|
output = "192.168.56.101:5555 device product:vbox86p model:device1 device:vbox86p"
|
27
|
-
expect(ParallelCalabash::AdbHelper.device_id_and_model(output)).to eq ["192.168.56.101:5555", "device1"]
|
27
|
+
expect(ParallelCalabash::AdbHelper.new([]).device_id_and_model(output)).to eq ["192.168.56.101:5555", "device1"]
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
describe :filter_device do
|
32
32
|
it 'should return devices if no filter is specified' do
|
33
33
|
device = ["192.168.56.101:5555", "device1"]
|
34
|
-
expect(ParallelCalabash::AdbHelper.
|
34
|
+
expect(ParallelCalabash::AdbHelper.new([]).filter_device(device)).to eq device
|
35
35
|
end
|
36
36
|
|
37
37
|
it 'should match devices that match the filter' do
|
38
38
|
device = ["192.168.56.101:5555", "device1"]
|
39
|
-
expect(ParallelCalabash::AdbHelper.
|
39
|
+
expect(ParallelCalabash::AdbHelper.new(["device1"]).filter_device(device)).to eq device
|
40
40
|
end
|
41
41
|
|
42
42
|
it 'should not return devices that do not match the filter' do
|
43
43
|
device = ["192.168.56.101:5555", "device1"]
|
44
|
-
expect(ParallelCalabash::AdbHelper.
|
44
|
+
expect(ParallelCalabash::AdbHelper.new(["notmatching"]).filter_device(device)).to eq nil
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'can also match on ip address' do
|
48
48
|
device = ["192.168.56.101:5555", "device1"]
|
49
|
-
expect(ParallelCalabash::AdbHelper.
|
49
|
+
expect(ParallelCalabash::AdbHelper.new(["192.168.56.101"]).filter_device(device)).to eq device
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
end
|
54
|
+
|
55
|
+
describe ParallelCalabash::IosHelper do
|
56
|
+
describe :test_apply_filter do
|
57
|
+
it 'Does nothing with no filters' do
|
58
|
+
expect(ParallelCalabash::IosHelper.new([], nil, {}, '')
|
59
|
+
.apply_filter([{any: 'thing'}, {what: 'soever'}]))
|
60
|
+
.to eq [{any: 'thing'}, {what: 'soever'}]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'Excludes anything not mentioned' do
|
64
|
+
expect(ParallelCalabash::IosHelper.new(['yes'], nil, {}, '')
|
65
|
+
.apply_filter([{any: 'thing'}, {what: 'soever'}]))
|
66
|
+
.to eq []
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'Excludes only things not mentioned' do
|
70
|
+
expect(ParallelCalabash::IosHelper.new(['aa', 'bb'], nil, {}, '')
|
71
|
+
.apply_filter([{eaa: 'thing', ecc: 'thing'}, {what: 'aa', so: 'ebb'}, {ever: 'thing'}]))
|
72
|
+
.to eq [{eaa: 'thing', ecc: 'thing'}, {what: 'aa', so: 'ebb'}]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe :test_remove_unconnected_devices do
|
77
|
+
it 'Removes unconnected devices' do
|
78
|
+
expect(ParallelCalabash::IosHelper.new(nil, nil, {},
|
79
|
+
"name [udid1]\nname2 [udid2]\nname3 [udid-unknown]")
|
80
|
+
.remove_unconnected_devices([{DEVICE_TARGET: 'udid1'},
|
81
|
+
{DEVICE_TARGET: 'udid2'},
|
82
|
+
{DEVICE_TARGET: 'udid-unplugged'}]))
|
83
|
+
.to eq [{DEVICE_TARGET: "udid1"}, {DEVICE_TARGET: "udid2"}]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe :test_compute_simulators do
|
88
|
+
it 'allocates ports to users' do
|
89
|
+
expect(ParallelCalabash::IosHelper.new([], {}, {USERS:['a', 'b'], INIT:'foo'}, '')
|
90
|
+
.compute_simulators)
|
91
|
+
.to eq [{USER:'a', INIT:'foo', CALABASH_SERVER_PORT: 28000},
|
92
|
+
{USER:'b', INIT:'foo', CALABASH_SERVER_PORT: 28001}]
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'allocates other ports to users' do
|
96
|
+
expect(ParallelCalabash::IosHelper.new([], {}, {USERS:['a', 'b'], INIT:'foo', CALABASH_SERVER_PORT:100}, '')
|
97
|
+
.compute_simulators)
|
98
|
+
.to eq [{USER:'a', INIT:'foo', CALABASH_SERVER_PORT: 100},
|
99
|
+
{USER:'b', INIT:'foo', CALABASH_SERVER_PORT: 101}]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe :test_compute_devices do
|
104
|
+
it 'returns nothing with no users' do
|
105
|
+
expect(ParallelCalabash::IosHelper.new([], {}, {DEVICES: [{DEVICE_TARGET: 'udid'}]}, 'name [udid]')
|
106
|
+
.compute_devices)
|
107
|
+
.to eq []
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'fails with no devices' do
|
111
|
+
expect { ParallelCalabash::IosHelper.new([], {}, {DEVICES: [{DEVICE_TARGET: 'udid'}],
|
112
|
+
USERS: ['a']}, 'name [udon\'t]')
|
113
|
+
.compute_devices }
|
114
|
+
.to raise_error(RuntimeError)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'allocates users to devices' do
|
118
|
+
expect(ParallelCalabash::IosHelper.new([], {}, {DEVICES: [{DEVICE_TARGET: 'udid'}],
|
119
|
+
USERS: ['a', 'b']}, 'name [udid]')
|
120
|
+
.compute_devices)
|
121
|
+
.to eq [{DEVICE_TARGET: "udid", USER: "a", INIT: ""}]
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'allocates users init to devices' do
|
125
|
+
expect(ParallelCalabash::IosHelper.new([], {}, {DEVICES: [{DEVICE_TARGET: 'udid'}],
|
126
|
+
USERS: ['a', 'b'],
|
127
|
+
INIT: 'start'}, 'name [udid]')
|
128
|
+
.compute_devices)
|
129
|
+
.to eq [{DEVICE_TARGET: "udid", USER: "a", INIT: "start"}]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -1,25 +1,30 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'minitest/mock'
|
2
3
|
require 'parallel_calabash'
|
3
4
|
|
4
5
|
describe ParallelCalabash::Runner do
|
5
6
|
describe :command_for_process do
|
6
7
|
it 'should return command with env variables' do
|
7
|
-
|
8
|
-
expect(
|
9
|
-
|
8
|
+
adb_helper = MiniTest::Mock.new
|
9
|
+
adb_helper.expect(:device_for_process, ["4d00fa3cb814c03f", "GT_N7100"], [0])
|
10
|
+
expect(ParallelCalabash::AndroidRunner.new(adb_helper, true)
|
11
|
+
.command_for_test(0, 'base_command', 'some.apk', '-some -options', %w(file1 file2)))
|
12
|
+
.to eq 'AUTOTEST=1;export AUTOTEST;ADB_DEVICE_ARG=4d00fa3cb814c03f;export ADB_DEVICE_ARG;'\
|
13
|
+
'DEVICE_INFO=GT_N7100;export DEVICE_INFO;TEST_PROCESS_NUMBER=1;export TEST_PROCESS_NUMBER;'\
|
14
|
+
'SCREENSHOT_PATH=4d00fa3cb814c03f_;export SCREENSHOT_PATH;'\
|
15
|
+
'base_command some.apk -some -options file1 file2'
|
10
16
|
end
|
11
17
|
end
|
12
18
|
|
13
|
-
|
14
19
|
describe :execute_command_for_process do
|
20
|
+
adb_helper = MiniTest::Mock.new
|
21
|
+
runner = ParallelCalabash::AndroidRunner.new(nil, true)
|
15
22
|
it 'should execute the command with correct env variables set and return exit status 0 when command gets executed successfully' do
|
16
|
-
|
17
|
-
expect(ParallelCalabash::Runner.execute_command_for_process(3,'echo $ADB_DEVICE_ARG;echo $TEST_PROCESS_NUMBER',true,nil)).to eq ({:stdout=>"DEVICE3\n4\n", :exit_status=>0})
|
23
|
+
expect(runner.execute_command_for_process(3, 'ADB_DEVICE_ARG=DEVICE3; export ADB_DEVICE_ARG; TEST_PROCESS_NUMBER=4; export TEST_PROCESS_NUMBER; echo $ADB_DEVICE_ARG;echo $TEST_PROCESS_NUMBER')).to eq ({:stdout=>"DEVICE3\n4\n", :exit_status=>0})
|
18
24
|
end
|
19
25
|
|
20
26
|
it 'should return exit status of 1' do
|
21
|
-
expect(
|
27
|
+
expect(runner.execute_command_for_process(3,"ruby -e 'exit(1)'")).to eq ({:stdout=>'', :exit_status=>1})
|
22
28
|
end
|
23
29
|
end
|
24
|
-
|
25
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_calabash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rajdeep
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -74,6 +74,15 @@ files:
|
|
74
74
|
- lib/parallel_calabash/result_formatter.rb
|
75
75
|
- lib/parallel_calabash/runner.rb
|
76
76
|
- lib/parallel_calabash/version.rb
|
77
|
+
- misc/autostart_test_users.app/Contents/Info.plist
|
78
|
+
- misc/autostart_test_users.app/Contents/MacOS/applet
|
79
|
+
- misc/autostart_test_users.app/Contents/PkgInfo
|
80
|
+
- misc/autostart_test_users.app/Contents/Resources/Scripts/main.scpt
|
81
|
+
- misc/autostart_test_users.app/Contents/Resources/applet.icns
|
82
|
+
- misc/autostart_test_users.app/Contents/Resources/applet.rsrc
|
83
|
+
- misc/autostart_test_users.app/Contents/Resources/description.rtfd/TXT.rtf
|
84
|
+
- misc/example_ios_config
|
85
|
+
- misc/sim_control.rb
|
77
86
|
- parallel_calabash.gemspec
|
78
87
|
- sample_output_with_4_processes.txt
|
79
88
|
- spec/lib/parallel_calabash/adb_helper_spec.rb
|