parallel_calabash 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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