Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[scan] Filter simulators with version greater than SDK version of active Xcode installation when choosing default #21677

Merged
merged 8 commits into from Dec 14, 2023
27 changes: 14 additions & 13 deletions fastlane_core/lib/fastlane_core/device_manager.rb
Expand Up @@ -7,13 +7,25 @@
module FastlaneCore
class DeviceManager
class << self
attr_reader :runtime_build_os_versions
def all(requested_os_type = "")
return connected_devices(requested_os_type) + simulators(requested_os_type)
end

def simulators(requested_os_type = "")
UI.verbose("Fetching available simulator devices")

output, status = Open3.capture2('xcrun simctl list runtimes -j')
begin
raise status unless status.success?
json = JSON.parse(output)
@runtime_build_os_versions = json['runtimes'].map { |h| [h['buildversion'], h['version']] }.to_h
rescue StandardError => e
UI.error(e)
UI.error('xcrun simctl CLI broken; cun `xcrun simctl list runtimes` and make sure it works')
UI.user_error!('xcrun simctl not working')
end

wuaar1003 marked this conversation as resolved.
Show resolved Hide resolved
@devices = []
os_type = 'unknown'
os_version = 'unknown'
Expand All @@ -22,18 +34,6 @@ def simulators(requested_os_type = "")
output = stdout.read
end

runtime_info = ''
Open3.popen3('xcrun simctl list runtimes') do |stdin, stdout, stderr, wait_thr|
# This regex outputs the version info in the format "<platform> <version><exact version>"
runtime_info = stdout.read.lines.map { |v| v.sub(/(\w+ \S+)\s*\((\S+)\s[\S\s]*/, "\\1 \\2") }.drop(1)
end
exact_versions = Hash.new({})
runtime_info.each do |r|
platform, general, exact = r.split
exact_versions[platform] = {} unless exact_versions.include?(platform)
exact_versions[platform][general] = exact
end

unless output.include?("== Devices ==")
UI.error("xcrun simctl CLI broken, run `xcrun simctl list devices` and make sure it works")
UI.user_error!("xcrun simctl not working.")
Expand All @@ -57,7 +57,7 @@ def simulators(requested_os_type = "")

if matches.count && (os_type == requested_os_type || requested_os_type == "")
# This is disabled here because the Device is defined later in the file, and that's a problem for the cop
@devices << Device.new(name: name, os_type: os_type, os_version: (exact_versions[os_type][os_version] || os_version), udid: udid, state: state, is_simulator: true)
@devices << Device.new(name: name, os_type: os_type, os_version: os_version, udid: udid, state: state, is_simulator: true)
end
end
end
Expand Down Expand Up @@ -289,6 +289,7 @@ def disable_slide_to_type(udid: nil, name: nil, os_version: nil)

def clear_cache
@devices = nil
@runtime_build_os_versions = nil
end

def launch(device)
Expand Down
107 changes: 56 additions & 51 deletions fastlane_core/spec/device_manager_spec.rb
Expand Up @@ -12,26 +12,35 @@
FastlaneCore::Simulator.clear_cache
end

it "raises an error if xcrun CLI prints garbage simulator" do
response = "response"
s = StringIO.new
s.puts(response)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, s, nil, nil)
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, s, nil, nil)
it 'raises an error if broken xcrun simctl list devices' do
status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

response = double('xcrun simctl list devices', read: 'garbage')
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)

expect do
devices = FastlaneCore::Simulator.all
end.to raise_error("xcrun simctl not working.")
end

it 'raises an error if broken xcrun simctl list runtimes' do
status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['garbage', status])

expect do
devices = FastlaneCore::Simulator.all
end.to raise_error(FastlaneCore::Interface::FastlaneError)
end

describe "properly parses the simctl output and generates Device objects for iOS simulator" do
it "Xcode 7" do
response = "response"
expect(response).to receive(:read).and_return(@simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::Simulator.all
expect(devices.count).to eq(6)
Expand Down Expand Up @@ -79,9 +88,9 @@
simctl_output = File.read('./fastlane_core/spec/fixtures/DeviceManagerSimctlOutputXcode8')
expect(response).to receive(:read).and_return(simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::Simulator.all
expect(devices.count).to eq(12)
Expand Down Expand Up @@ -111,9 +120,9 @@
simctl_output = File.read('./fastlane_core/spec/fixtures/DeviceManagerSimctlOutputXcode9')
expect(response).to receive(:read).and_return(simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::Simulator.all
expect(devices.count).to eq(15)
Expand Down Expand Up @@ -143,9 +152,9 @@
simctl_output = File.read('./fastlane_core/spec/fixtures/DeviceManagerSimctlOutputXcode11')
expect(response).to receive(:read).and_return(simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::Simulator.all
expect(devices.count).to eq(29)
Expand Down Expand Up @@ -175,9 +184,9 @@
response = "response"
expect(response).to receive(:read).and_return(@simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::SimulatorTV.all
expect(devices.count).to eq(1)
Expand All @@ -194,9 +203,9 @@
response = "response"
expect(response).to receive(:read).and_return(@simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::SimulatorWatch.all
expect(devices.count).to eq(2)
Expand All @@ -219,9 +228,9 @@
response = "response"
expect(response).to receive(:read).and_return(@simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::DeviceManager.simulators
expect(devices.count).to eq(9)
Expand Down Expand Up @@ -275,9 +284,9 @@
simctl_output = File.read('./fastlane_core/spec/fixtures/DeviceManagerSimctlOutputXcode10BootedUnavailable')
expect(response).to receive(:read).and_return(simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::DeviceManager.simulators
expect(devices.count).to eq(3)
Expand Down Expand Up @@ -384,9 +393,9 @@

expect(response).to receive(:read).and_return(@simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::DeviceManager.all('iOS')
expect(devices.count).to eq(8)
Expand Down Expand Up @@ -439,9 +448,9 @@

expect(response).to receive(:read).and_return(@simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("line\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)

status = double('status', "success?": true)
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return(['{"runtimes": [] }', status])

devices = FastlaneCore::DeviceManager.all('tvOS')
expect(devices.count).to eq(2)
Expand All @@ -460,23 +469,19 @@
)
end

it "parses runtime information properly to get the exact version information" do
response = "response"
expect(response).to receive(:read).and_return(@simctl_output)
expect(Open3).to receive(:popen3).with("xcrun simctl list devices").and_yield(nil, response, nil, nil)
thing = {}
expect(thing).to receive(:read).and_return("== Runtimes ==\ntvOS 9.0 (9.0.1 - 13A345) - com.apple.CoreSimulator.SimRuntime.tvOS-9-0\n")
allow(Open3).to receive(:popen3).with("xcrun simctl list runtimes").and_yield(nil, thing, nil, nil)
it 'properly parses `xcrun simctl list runtimes` to associate runtime builds with their exact OS version' do
response = double('xcrun simctl list devices', read: '== Devices ==')
allow(Open3).to receive(:popen3).with('xcrun simctl list devices').and_yield(nil, response, nil, nil)

devices = FastlaneCore::SimulatorTV.all
expect(devices.count).to eq(1)
status = double('status', "success?": true)
runtime_output = File.read('./fastlane_core/spec/fixtures/XcrunSimctlListRuntimesOutput')
expect(Open3).to receive(:capture2).with("xcrun simctl list runtimes -j").and_return([runtime_output, status])

expect(devices[0]).to have_attributes(
name: "Apple TV 1080p", os_type: "tvOS", os_version: "9.0.1",
udid: "D239A51B-A61C-4B60-B4D6-B7EC16595128",
state: "Shutdown",
is_simulator: true
)
devices = FastlaneCore::DeviceManager.simulators

expect(FastlaneCore::DeviceManager.runtime_build_os_versions['21A328']).to eq('17.0')
expect(FastlaneCore::DeviceManager.runtime_build_os_versions['21A342']).to eq('17.0.1')
expect(FastlaneCore::DeviceManager.runtime_build_os_versions['21R355']).to eq('10.0')
end

describe FastlaneCore::DeviceManager::Device do
Expand Down