parallel_calabash 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|