def test_select_option(): # Return '3' when prompted console = DummyConsole("3") options = { "first": "The first option", "second": "The second option", "third": "The third option", "fourth": "The fourth option", } result = select_option(options, input=console) # Input is requested once assert console.prompts == ["> "] # Alphabetically, option 3 will be "the second option" assert result == "second"
def test_select_option(): # Return '3' when prompted mock_input = mock.MagicMock(return_value='3') options = { 'first': 'The first option', 'second': 'The second option', 'third': 'The third option', 'fourth': 'The fourth option', } result = select_option(options, input=mock_input) # Input is requested once assert mock_input.call_count == 1 # Alphabetically, option 3 will be "the second option" assert result == 'second'
def test_select_option(): # Return '3' when prompted console = DummyConsole('3') options = { 'first': 'The first option', 'second': 'The second option', 'third': 'The third option', 'fourth': 'The fourth option', } result = select_option(options, input=console) # Input is requested once assert console.prompts == ['> '] # Alphabetically, option 3 will be "the second option" assert result == 'second'
def test_select_option_list(): "If select_option is given a list of tuples, they're presented as provided" # Return '3' when prompted console = DummyConsole('3') options = [ ('first', 'The first option'), ('second', 'The second option'), ('third', 'The third option'), ('fourth', 'The fourth option'), ] result = select_option(options, input=console) # Input is requested once assert console.prompts == ['> '] # The third option is the third option :-) assert result == 'third'
def test_select_option_list(): """If select_option is given a list of tuples, they're presented as provided.""" # Return '3' when prompted console = DummyConsole("3") options = [ ("first", "The first option"), ("second", "The second option"), ("third", "The third option"), ("fourth", "The fourth option"), ] result = select_option(options, input=console) # Input is requested once assert console.prompts == ["> "] # The third option is the third option :-) assert result == "third"
def select_identity(self, identity=None): """ Get the codesigning identity to use. :param identity: A pre-specified identity (either the 40-digit hex checksum, or the string name of the identity). If provided, it will be validated against the list of available identities to confirm that it is a valid codesigning identity. :returns: The final identity to use """ # Obtain the valid codesigning identities. identities = self.get_identities('codesigning') if identity: try: # Try to look up the identity as a hex checksum return identities[identity] except KeyError: # It's not a valid checksum; try to use it as a value. if identity in identities.values(): return identity raise BriefcaseCommandError( "Invalid code signing identity {identity!r}".format( identity=identity ) ) if len(identities) == 0: raise BriefcaseCommandError( "No code signing identities are available." ) elif len(identities) == 1: identity = list(identities.items())[0][1] else: print() print("Select code signing identity to use:") print() selection = select_option(identities, input=self.input) identity = identities[selection] print("selected", identity) return identity
def test_select_option_bad_input(): # In order, return: # blank # 'asdf' # '10' # '3' console = DummyConsole("", "asdf", "10", "3") options = { "first": "The first option", "second": "The second option", "third": "The third option", "fourth": "The fourth option", } result = select_option(options, input=console) # Input is requested five times; first four cause errors. assert console.prompts == ["> "] * 4 # Alphabetically, option 3 will be "the second option" assert result == "second"
def test_select_option_bad_input(): # In order, return: # blank # 'asdf' # '10' # '3' mock_input = mock.MagicMock(side_effect=['', 'asdf', '10', '3']) options = { 'first': 'The first option', 'second': 'The second option', 'third': 'The third option', 'fourth': 'The fourth option', } result = select_option(options, input=mock_input) # Input is requested five times; first four cause errors. assert mock_input.call_count == 4 # Alphabetically, option 3 will be "the second option" assert result == 'second'
def test_select_option_bad_input(): # In order, return: # blank # 'asdf' # '10' # '3' console = DummyConsole('', 'asdf', '10', '3') options = { 'first': 'The first option', 'second': 'The second option', 'third': 'The third option', 'fourth': 'The fourth option', } result = select_option(options, input=console) # Input is requested five times; first four cause errors. assert console.prompts == ['> '] * 4 # Alphabetically, option 3 will be "the second option" assert result == 'second'
def select_identity(self, identity=None): """Get the codesigning identity to use. :param identity: A pre-specified identity (either the 40-digit hex checksum, or the string name of the identity). If provided, it will be validated against the list of available identities to confirm that it is a valid codesigning identity. :returns: The final identity to use """ # Obtain the valid codesigning identities. identities = self.get_identities(self, "codesigning") if identity: try: # Try to look up the identity as a hex checksum return identities[identity] except KeyError as e: # Try to look up the identity as readable name if identity in identities.values(): return identity # Not found raise BriefcaseCommandError( f"Invalid code signing identity {identity!r}" ) from e if len(identities) == 0: raise BriefcaseCommandError("No code signing identities are available.") elif len(identities) == 1: identity = list(identities.items())[0][1] else: self.input.prompt() self.input.prompt("Select code signing identity to use:") self.input.prompt() selection = select_option(identities, input=self.input) identity = identities[selection] self.logger.info(f"selected {identity}") return identity
def select_target_device(self, device_or_avd): """ Select a device to be the target for actions. Interrogates the system to get the list of available devices. If the user has specified a device at the command line, that device will be validated, and then automatically selected. :param device_or_avd: The device or AVD to target. Can be a physical device id (a hex string), an emulator id ("emulator-5554"), or an emulator AVD name ("@robotfriend"). If ``None``, the user will be asked to select a device from the list available. :returns: A tuple containing ``(device, name, avd)``. ``avd`` will only be provided if an emulator with that AVD is not currently running. If ``device`` is null, a new emulator should be created. """ # Get the list of attached devices (includes running emulators) running_devices = self.devices() # Choices is an ordered list of options that can be shown to the user. # Each device should appear only once, and be keyed by AVD only if # a device ID isn't available. choices = [] # Device choices is the full lookup list. Devices can be looked up # by any valid key - ID *or* AVD. device_choices = {} # Iterate over all the running devices. # If the device is a virtual device, use ADB to get the emulator AVD name. # If it is a physical device, use the device name. # Keep a log of all running AVDs running_avds = {} for d, details in sorted(running_devices.items(), key=lambda d: d[1]["name"]): name = details["name"] avd = self.adb(d).avd_name() if avd: # It's a running emulator running_avds[avd] = d full_name = "@{avd} (running emulator)".format(avd=avd, ) choices.append((d, full_name)) # Save the AVD as a device detail. details["avd"] = avd # Device can be looked up by device ID or AVD device_choices[d] = full_name device_choices["@" + avd] = full_name else: # It's a physical device (might be disabled) full_name = "{name} ({d})".format(name=name, d=d) choices.append((d, full_name)) device_choices[d] = full_name # Add any non-running emulator AVDs to the list of candidate devices for avd in self.emulators(): if avd not in running_avds: name = "@{avd} (emulator)".format(avd=avd) choices.append(("@" + avd, name)) device_choices["@" + avd] = name # If a device or AVD has been provided, check it against the available # device list. if device_or_avd: try: name = device_choices[device_or_avd] if device_or_avd.startswith("@"): # specifier is an AVD try: avd = device_or_avd[1:] device = running_avds[avd] except KeyError: # device_or_avd isn't in the list of running avds; # it must be a non-running emulator. return None, name, avd else: # Specifier is a direct device ID avd = None device = device_or_avd details = running_devices[device] avd = details.get("avd") if details["authorized"]: # An authorized, running device (emulator or physical) return device, name, avd else: # An unauthorized physical device raise AndroidDeviceNotAuthorized(device) except KeyError: # Provided device_or_id isn't a valid device identifier. if device_or_avd.startswith("@"): id_type = "emulator AVD" else: id_type = "device ID" raise InvalidDeviceError(id_type, device_or_avd) # We weren't given a device/AVD; we have to select from the list. # If we're selecting from a list, there's always one last choice choices.append((None, "Create a new Android emulator")) # Show the choices to the user. print() print("Select device:") print() try: choice = select_option(choices, input=self.command.input) except InputDisabled: # If input is disabled, and there's only one actual simulator, # select it. If there are no simulators, select "Create simulator" if len(choices) <= 2: choice = choices[0][0] else: raise BriefcaseCommandError( "Input has been disabled; can't select a device to target." ) # Proces the user's choice if choice is None: # Create a new emulator. No device ID or AVD. device = None avd = None name = None elif choice.startswith("@"): # A non-running emulator. We have an AVD, but no device ID. device = None name = device_choices[choice] avd = choice[1:] else: # Either a running emulator, or a physical device. Regardless, # we need to check if the device is developer enabled try: details = running_devices[choice] if not details["authorized"]: # An unauthorized physical device raise AndroidDeviceNotAuthorized(choice) # Return the device ID and name. device = choice name = device_choices[choice] avd = details.get("avd") except KeyError: raise InvalidDeviceError("device ID", choice) if avd: print(""" In future, you can specify this device by running: briefcase run android -d @{avd} """.format(avd=avd)) elif device: print(""" In future, you can specify this device by running: briefcase run android -d {device} """.format(device=device)) return device, name, avd
def select_target_device(self, udid_or_device=None): """ Select the target device to use for iOS builds. Interrogates the system to get the list of available simulators If there is only a single iOS version available, that version will be selected automatically. If there is only one simulator available, that version will be selected automatically. If the user has specified a device at the command line, it will be used in preference to any :param udid_or_device: The device to target. Can be a device UUID, a device name ("iPhone 11"), or a device name and OS version ("iPhone 11::13.3"). If ``None``, the user will be asked to select a device at runtime. :returns: A tuple containing the udid, iOS version, and device name for the selected device. """ simulators = self.get_simulators(self, 'iOS') try: # Try to convert to a UDID. If this succeeds, then the argument # is a UDID. udid = str(UUID(udid_or_device)).upper() # User has provided a UDID at the command line; look for it. for iOS_version, devices in simulators.items(): try: device = devices[udid] return udid, iOS_version, device except KeyError: # UDID doesn't exist in this iOS version; try another. pass # We've iterated through all available iOS versions and # found no match; return an error. raise InvalidDeviceError('device UDID', udid) except (ValueError, TypeError): # Provided value wasn't a UDID. # It must be a device or device+version if udid_or_device and '::' in udid_or_device: # A device name::version. device, iOS_version = udid_or_device.split('::') try: devices = simulators[iOS_version] try: # Do a reverse lookup for UDID, based on a # case-insensitive name lookup. udid = { name.lower(): udid for udid, name in devices.items() }[device.lower()] # Found a match; # normalize back to the official name and return. device = devices[udid] return udid, iOS_version, device except KeyError: raise InvalidDeviceError('device name', device) except KeyError: raise InvalidDeviceError('iOS Version', iOS_version) elif udid_or_device: # Just a device name device = udid_or_device # Search iOS versions, looking for most recent version first. for iOS_version, devices in sorted( simulators.items(), key=lambda item: tuple( int(v) for v in item[0].split('.')), reverse=True): try: udid = { name.lower(): udid for udid, name in devices.items() }[device.lower()] # Found a match; # normalize back to the official name and return. device = devices[udid] return udid, iOS_version, device except KeyError: # UDID doesn't exist in this iOS version; try another. pass raise InvalidDeviceError('device name', device) if len(simulators) == 0: raise BriefcaseCommandError("No iOS simulators available.") elif len(simulators) == 1: iOS_version = list(simulators.keys())[0] else: if self.input.enabled: print() print("Select iOS version:") print() iOS_version = select_option( {version: version for version in simulators.keys()}, input=self.input) devices = simulators[iOS_version] if len(devices) == 0: raise BriefcaseCommandError( "No simulators available for iOS {iOS_version}.".format( iOS_version=iOS_version)) elif len(devices) == 1: udid = list(devices.keys())[0] else: if self.input.enabled: print() print("Select simulator device:") print() udid = select_option(devices, input=self.input) device = devices[udid] print("In future, you could specify this device by running:") print() print(' briefcase {self.command} iOS -d "{device}::{iOS_version}"'. format(self=self, device=device, iOS_version=iOS_version)) print() print('or:') print() print(" briefcase {self.command} iOS -d {udid}".format(self=self, udid=udid)) return udid, iOS_version, device
def select_target_device(self, udid_or_device=None): """Select the target device to use for iOS builds. Interrogates the system to get the list of available simulators If there is only a single iOS version available, that version will be selected automatically. If there is only one simulator available, that version will be selected automatically. If the user has specified a device at the command line, it will be used in preference to any :param udid_or_device: The device to target. Can be a device UUID, a device name ("iPhone 11"), or a device name and OS version ("iPhone 11::13.3"). If ``None``, the user will be asked to select a device at runtime. :returns: A tuple containing the udid, iOS version, and device name for the selected device. """ simulators = self.get_simulators(self, "iOS") try: # Try to convert to a UDID. If this succeeds, then the argument # is a UDID. udid = str(UUID(udid_or_device)).upper() # User has provided a UDID at the command line; look for it. for iOS_version, devices in simulators.items(): try: device = devices[udid] return udid, iOS_version, device except KeyError: # UDID doesn't exist in this iOS version; try another. pass # We've iterated through all available iOS versions and # found no match; return an error. raise InvalidDeviceError("device UDID", udid) except (ValueError, TypeError) as e: # Provided value wasn't a UDID. # It must be a device or device+version if udid_or_device and "::" in udid_or_device: # A device name::version. device, iOS_version = udid_or_device.split("::") try: # Convert the simulator dict into a dict where # the iOS versions are lower cased, then do a lookup # on the lower case name provided by the user. # However, also return the *unmodified* iOS version string # so we can convert the user-provided iOS version into the # "clean", official capitalization. iOS_version, devices = { clean_iOS_version.lower(): (clean_iOS_version, details) for clean_iOS_version, details in simulators.items() }[iOS_version.lower()] try: # Do a reverse lookup for UDID, based on a # case-insensitive name lookup. udid = {name.lower(): udid for udid, name in devices.items()}[ device.lower() ] # Found a match; # normalize back to the official name and return. device = devices[udid] return udid, iOS_version, device except KeyError as e: raise InvalidDeviceError("device name", device) from e except KeyError as err: raise InvalidDeviceError("iOS Version", iOS_version) from err elif udid_or_device: # Just a device name device = udid_or_device # Search iOS versions, looking for most recent version first. # The iOS version string will be something like "iOS 15.4"; # Drop the prefix (if it exists), convert into the tuple (15, 4), # and sort numerically. for iOS_version, devices in sorted( simulators.items(), key=lambda item: tuple( int(v) for v in item[0].split()[-1].split(".") ), reverse=True, ): try: udid = {name.lower(): udid for udid, name in devices.items()}[ device.lower() ] # Found a match; # normalize back to the official name and return. device = devices[udid] return udid, iOS_version, device except KeyError: # UDID doesn't exist in this iOS version; try another. pass raise InvalidDeviceError("device name", device) from e if len(simulators) == 0: raise BriefcaseCommandError("No iOS simulators available.") elif len(simulators) == 1: iOS_version = list(simulators.keys())[0] else: self.input.prompt() self.input.prompt("Select iOS version:") self.input.prompt() iOS_version = select_option( {version: version for version in simulators.keys()}, input=self.input ) devices = simulators[iOS_version] if len(devices) == 0: raise BriefcaseCommandError(f"No simulators available for {iOS_version}.") elif len(devices) == 1: udid = list(devices.keys())[0] else: self.input.prompt() self.input.prompt("Select simulator device:") self.input.prompt() udid = select_option(devices, input=self.input) device = devices[udid] self.logger.info( f""" In the future, you could specify this device by running: briefcase {self.command} iOS -d "{device}::{iOS_version}" or: briefcase {self.command} iOS -d {udid} """ ) return udid, iOS_version, device