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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5e86fb602e2958bb44e857ec0cb81c1755904e1
4
- data.tar.gz: 79a70e5354ac574d03691b929e8025d1c8e0106a
3
+ metadata.gz: 9d3c746855a83738b07bf9cf32ec7b325a4f395b
4
+ data.tar.gz: deb3562fc74e7b1e3d013eb98fa0b8148d93c15b
5
5
  SHA512:
6
- metadata.gz: d26a69315b401ecc609ab9c8a3a934754276f1fb037b7d711da7ac5e165e7f809d533b71708879f6ae5b60de31c10973a37a000c0ced03ccc13d1ab82a1c65ea
7
- data.tar.gz: 24bf3870f338d3a75664d33da1e607a5f08cc53642159956b54142a8ae335a48f8442b5c524fd27228617c49f6ba7e1562327647a4233745a9a8c440948ecd18
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 -a my.apk -o'--format pretty' features/ --serialize-stdout
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
- ## REPROTING
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
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  $LOAD_PATH << File.expand_path("../../lib", __FILE__)
4
- require "parallel_calabash"
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
- opts.on("-d", "--distribution-tag tag", "divide features into groups as per occurrence of given tag") do |distribution_tag|
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
- puts opt_parser.help
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 options
94
+ ParallelCalabash::ParallelCalabashApp.new(options).run_tests_in_parallel
65
95
 
66
96
 
@@ -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
- def number_of_processes_to_start options
16
- number_of_processes = AdbHelper.number_of_connected_devices(options[:filter])
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(options)
25
- number_of_processes = number_of_processes_to_start(options)
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
- test_results = Parallel.map_with_index(groups, :in_threads => threads) do |group, index|
33
- Runner.run_tests(group, index, options)
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 AdbHelper
3
- class << self
4
+ module DevicesHelper
5
+ def device_for_process process_num
6
+ connected_devices_with_model_info[process_num]
7
+ end
4
8
 
5
- def device_for_process process_num, filter=[]
6
- connected_devices_with_model_info(filter)[process_num]
7
- end
9
+ def number_of_connected_devices
10
+ connected_devices_with_model_info.size
11
+ end
12
+ end
8
13
 
9
- def number_of_connected_devices filter=[]
10
- connected_devices_with_model_info(filter).size
11
- end
14
+ class AdbHelper
15
+ include ParallelCalabash::DevicesHelper
16
+
17
+ def initialize(filter = [])
18
+ @filter = filter
19
+ end
12
20
 
13
- def connected_devices_with_model_info filter
14
- begin
15
- list =
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, filter)
26
+ filter_device(device)
19
27
  end
20
- list.compact
21
- rescue
22
- []
23
- end
28
+ list.compact
29
+ rescue
30
+ []
24
31
  end
32
+ end
25
33
 
26
- def device_id_and_model line
27
- if line.match(/device(?!s)/)
28
- [line.split(" ").first,line.scan(/model:(.*) device/).flatten.first]
29
- end
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
- def filter_device device, filter
33
- if filter && !filter.empty? && device
34
- device unless filter.collect{|f| device[0].match(f) || device[1].match(f) }.compact.empty?
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
- device
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"].map { |example|
52
- "#{feature["uri"]}:#{example["line"]}"
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
- `cucumber #{options[:cucumber_options]} -f usage --dry-run -f json --out parallel_calabash_dry_run.json #{options[:feature_folder].join(' ')}`
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)
@@ -5,7 +5,7 @@ module ParallelCalabash
5
5
 
6
6
  def report_results(test_results)
7
7
  results = find_results(test_results.map { |result| result[:stdout] }.join(''))
8
- puts ""
8
+ puts ''
9
9
  puts summarize_results(results)
10
10
  end
11
11
 
@@ -1,52 +1,224 @@
1
+ require 'fileutils'
2
+ require 'find'
3
+
1
4
  module ParallelCalabash
2
- class Runner
3
- class << self
4
- def base_command
5
- 'calabash-android run'
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
- def run_tests(test_files, process_number, options)
9
- cmd = [base_command, options[:apk_path], options[:cucumber_options], *test_files].compact*' '
10
- execute_command_for_process(process_number, cmd, options[:mute_output], options[:filter])
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
- def execute_command_for_process(process_number, cmd, silence, filter)
14
- command_for_current_process = command_for_process(process_number, cmd, filter)
15
- output = open("|#{command_for_current_process}", "r") { |output| show_output(output, silence) }
16
- exitstatus = $?.exitstatus
165
+ def version(simulator)
166
+ simulator.match('\d+-\d+$').to_s.gsub('-', '.')
167
+ end
17
168
 
18
- if silence
19
- $stdout.print output
20
- $stdout.flush
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
- def command_for_process(process_number, cmd, filter)
27
- env = {}
28
- device_id, device_info = ParallelCalabash::AdbHelper.device_for_process process_number, filter
29
- env = env.merge({'AUTOTEST' => '1', 'ADB_DEVICE_ARG' => device_id, 'DEVICE_INFO' => device_info, "TEST_PROCESS_NUMBER" => (process_number+1).to_s})
30
- separator = (WINDOWS ? ' & ' : ';')
31
- exports = env.map { |k, v| WINDOWS ? "(SET \"#{k}=#{v}\")" : "#{k}=#{v};export #{k}" }.join(separator)
32
- exports + separator + cmd
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
- def show_output(output, silence)
36
- result = ""
37
- loop do
38
- begin
39
- read = output.readpartial(1000000) # read whatever chunk we can get
40
- result << read
41
- unless silence
42
- $stdout.print read
43
- $stdout.flush
44
- end
45
- end
46
- end rescue EOFError
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
@@ -1,3 +1,3 @@
1
1
  module ParallelCalabash
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  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>
@@ -0,0 +1,4 @@
1
+ {\rtf1\ansi\ansicpg1252\cocoartf1347\cocoasubrtf570
2
+ {\fonttbl}
3
+ {\colortbl;\red255\green255\blue255;}
4
+ }
@@ -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
+ }
@@ -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.filter_device(device, [])).to eq device
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.filter_device(device, ["device1"])).to eq device
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.filter_device(device, ["notmatching"])).to eq nil
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.filter_device(device, ["192.168.56.101"])).to eq device
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
- ParallelCalabash::AdbHelper.should_receive(:device_for_process).with(0,nil).and_return(["4d00fa3cb814c03f", "GT_N7100"])
8
- expect(ParallelCalabash::Runner.command_for_process(0, 'base_command', nil)).to eq \
9
- "AUTOTEST=1;export AUTOTEST;ADB_DEVICE_ARG=4d00fa3cb814c03f;export ADB_DEVICE_ARG;DEVICE_INFO=GT_N7100;export DEVICE_INFO;TEST_PROCESS_NUMBER=1;export TEST_PROCESS_NUMBER;base_command"
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
- ParallelCalabash::AdbHelper.should_receive(:device_for_process).with(3,nil).and_return("DEVICE3")
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(ParallelCalabash::Runner.execute_command_for_process(3,"ruby -e 'exit(1)'",true,nil)).to eq ({:stdout=>"", :exit_status=>1})
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.1.4
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-06-24 00:00:00.000000000 Z
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