├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── circle.yml ├── fastlane-plugin-automated_test_emulator_run.gemspec ├── fastlane ├── Fastfile ├── Pluginfile └── examples │ └── AVD_setup.json ├── lib └── fastlane │ └── plugin │ ├── automated_test_emulator_run.rb │ └── automated_test_emulator_run │ ├── actions │ └── automated_test_emulator_run_action.rb │ ├── factory │ ├── adb_controller_factory.rb │ └── avd_controller_factory.rb │ ├── provider │ └── avd_setup_provider.rb │ └── version.rb └── spec ├── automated_test_emulator_run_action_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | 4 | ## Documentation cache and generated files: 5 | /.yardoc/ 6 | /_yardoc/ 7 | /doc/ 8 | /rdoc/ 9 | fastlane/README.md 10 | fastlane/report.xml 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --color 3 | --format d 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # kind_of? is a good way to check a type 2 | Style/ClassCheck: 3 | EnforcedStyle: kind_of? 4 | 5 | # It's better to be more explicit about the type 6 | Style/BracesAroundHashParameters: 7 | Enabled: false 8 | 9 | # specs sometimes have useless assignments, which is fine 10 | Lint/UselessAssignment: 11 | Exclude: 12 | - '**/spec/**/*' 13 | 14 | # We could potentially enable the 2 below: 15 | Style/IndentHash: 16 | Enabled: false 17 | 18 | Style/AlignHash: 19 | Enabled: false 20 | 21 | # HoundCI doesn't like this rule 22 | Style/DotPosition: 23 | Enabled: false 24 | 25 | # We allow !! as it's an easy way to convert ot boolean 26 | Style/DoubleNegation: 27 | Enabled: false 28 | 29 | # Sometimes we allow a rescue block that doesn't contain code 30 | Lint/HandleExceptions: 31 | Enabled: false 32 | 33 | # Cop supports --auto-correct. 34 | Lint/UnusedBlockArgument: 35 | Enabled: false 36 | 37 | # Needed for $verbose 38 | Style/GlobalVars: 39 | Enabled: false 40 | 41 | # We want to allow class Fastlane::Class 42 | Style/ClassAndModuleChildren: 43 | Enabled: false 44 | 45 | # $? Exit 46 | Style/SpecialGlobalVars: 47 | Enabled: false 48 | 49 | Metrics/AbcSize: 50 | Max: 60 51 | Exclude: 52 | - '**/lib/*/options.rb' 53 | 54 | # The %w might be confusing for new users 55 | Style/WordArray: 56 | MinSize: 19 57 | 58 | # raise and fail are both okay 59 | Style/SignalException: 60 | Enabled: false 61 | 62 | # Better too much 'return' than one missing 63 | Style/RedundantReturn: 64 | Enabled: false 65 | 66 | # Having if in the same line might not always be good 67 | Style/IfUnlessModifier: 68 | Enabled: false 69 | 70 | # and and or is okay 71 | Style/AndOr: 72 | Enabled: false 73 | 74 | # Configuration parameters: CountComments. 75 | Metrics/ClassLength: 76 | Max: 320 77 | 78 | Metrics/CyclomaticComplexity: 79 | Max: 17 80 | 81 | # Configuration parameters: AllowURI, URISchemes. 82 | Metrics/LineLength: 83 | Max: 370 84 | 85 | # Configuration parameters: CountKeywordArgs. 86 | Metrics/ParameterLists: 87 | Max: 17 88 | 89 | Metrics/PerceivedComplexity: 90 | Max: 18 91 | 92 | # Sometimes it's easier to read without guards 93 | Style/GuardClause: 94 | Enabled: false 95 | 96 | # We allow both " and ' 97 | Style/StringLiterals: 98 | Enabled: false 99 | 100 | # something = if something_else 101 | # that's confusing 102 | Style/ConditionalAssignment: 103 | Enabled: false 104 | 105 | # Better to have too much self than missing a self 106 | Style/RedundantSelf: 107 | Enabled: false 108 | 109 | # e.g. 110 | # def self.is_supported?(platform) 111 | # we may never use `platform` 112 | Lint/UnusedMethodArgument: 113 | Enabled: false 114 | 115 | # the let(:key) { ... } 116 | Lint/ParenthesesAsGroupedExpression: 117 | Exclude: 118 | - '**/spec/**/*' 119 | 120 | # This would reject is_ in front of methods 121 | # We use `is_supported?` everywhere already 122 | Style/PredicateName: 123 | Enabled: false 124 | 125 | # We allow the $ 126 | Style/PerlBackrefs: 127 | Enabled: false 128 | 129 | # Disable '+ should be surrounded with a single space' for xcodebuild_spec.rb 130 | Style/SpaceAroundOperators: 131 | Exclude: 132 | - '**/spec/actions_specs/xcodebuild_spec.rb' 133 | 134 | Metrics/MethodLength: 135 | Exclude: 136 | - '**/lib/fastlane/actions/*.rb' 137 | - '**/bin/fastlane' 138 | - '**/lib/*/options.rb' 139 | Max: 60 140 | 141 | AllCops: 142 | Include: 143 | - '**/fastlane/Fastfile' 144 | Exclude: 145 | - '**/lib/assets/custom_action_template.rb' 146 | - './vendor/**/*' 147 | 148 | # We're not there yet 149 | Style/Documentation: 150 | Enabled: false 151 | 152 | # Added after upgrade to 0.38.0 153 | Style/MutableConstant: 154 | Enabled: false 155 | 156 | # length > 0 is good 157 | Style/ZeroLengthPredicate: 158 | Enabled: false 159 | 160 | # Adds complexity 161 | Style/IfInsideElse: 162 | Enabled: false -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # os: osx # enable this if you need macOS support 2 | language: ruby 3 | rvm: 4 | - 2.2.4 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 6 | eval(File.read(plugins_path), binding) if File.exist?(plugins_path) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kamil Krzyk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # automated_test_emulator_run plugin 2 | 3 | [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-automated_test_emulator_run) 4 | 5 | (article is deprecated - covered plugin version < 1.3.2 which doesn't support Build-Tools ver. >= 25.0.2) 6 |
See [blog post related to this plugin](https://medium.com/azimolabs/managing-android-virtual-devices-during-test-session-98a403acffc2#.upcmonil1). You can learn there how to create basic setup for this plugin step by step. 7 | 8 | ## About automated_test_emulator_run 9 | 10 | Starts any number of AVDs. AVDs are created and configured automatically according to user liking before instrumentation test process starts (started either via shell command or from gradle) and killed/deleted after test process finishes. 11 | 12 | ## Getting Started 13 | 14 | This project is a [fastlane](https://github.com/fastlane/fastlane) plugin. 15 | 16 | 1. To get started with `fastlane-plugin-automated_test_emulator_run`, add it to your project by running: 17 | 18 | ```bash 19 | fastlane add_plugin automated_test_emulator_run 20 | ``` 21 | 2. Create your \*.JSON config file to create AVD launch plan according to schema below/provided example. 22 | 23 | 3. Wrap your test launch command with plugin and provide link to \*.JSON config. 24 | 25 | ## Example of Fastfile 26 | 27 | Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`. 28 | 29 | ## JSON config 30 | 31 | What is JSON config? 32 | 33 | It is a core of this plugin. User can specify any number of AVD devices in JSON file. Each AVD can be configured separately. Plugin will read JSON file and create fresh, new, untouched AVDs on host - use them in tests - and then delete them after test process finishes. 34 | 35 | JSON file scheme: 36 | ``` 37 | { 38 | "avd_list": 39 | [ 40 | { 41 | "avd_name": "", 42 | 43 | "create_avd_package": "", 44 | "create_avd_device": "", 45 | "create_avd_tag": "", 46 | "create_avd_abi": "", 47 | "create_avd_additional_options": "", 48 | "create_avd_hardware_config_filepath": "", 49 | 50 | "launch_avd_port": "", 51 | "launch_avd_snapshot_filepath": "", 52 | "launch_avd_launch_binary_name": "", 53 | "launch_avd_additional_options": "" 54 | } 55 | ] 56 | } 57 | ``` 58 | 59 | Parameters: 60 |
For official help refer to `avdmanager` binary file: `/tools/bin/avdmanager create avd` 61 | - `avd_name` - name of your AVD, avoid using spaces, this field is necessary 62 | - `create_avd_package` - path to system image in example "system-images;android-23;google_apis;x86_64" 63 | - `create_avd_device` - name of your device visible on `avdmanager list device` list 64 | - `create_avd_tag` - the sys-img tag to use for the AVD. e.g. if you are using Google Apis then set it to "google_apis" 65 | - `create_avd_abi` - abi for AVD e.g. "x86" or "x86_64" (https://developer.android.com/ndk/guides/abis.html) 66 | - `create_avd_hardware_config_filepath` - path to config.ini file containing custom config for your AVD. After AVD is created this file will be copied into AVD location before it launches. 67 | - `create_avd_additional_options` - if you think that you need something more you can just add your create parameters here (e.g. "--sdcard 128M", https://developer.android.com/studio/tools/help/android.html) 68 | - `launch_avd_snapshot_filepath` - plugin might (if you set it) delete and re-create AVD before test start. That means all your permissions and settings will be lost on each emulator run. If you want to apply qemu image with saved AVD state you can put path to it in this field. It will be applied by using "-wipe-data -initdata " 69 | - `launch_avd_launch_binary_name` - depending on your CPU architecture you need to choose binary file which should launch your AVD (e.g. "emulator", "emulator64-arm") 70 | - `launch_avd_port` - port on which you wish your AVD should be launched, if you leave this field empty it will be assigned automatically 71 | - `launch_avd_additional_options` - if you need more customization add your parameters here (e.g. "-gpu on -no-boot-anim -no-window", https://developer.android.com/studio/run/emulator-commandline.html) 72 | 73 | Note: 74 | - parameter `--path` is not supported, if you want to change directory to where your AVD are created edit your env variable ANDROID_SDK_HOME. Which is set to `~/.android/avd` by default. 75 | 76 | Hints: 77 | - After change from `android` bin to `avdmanager` bin, default settings of AVD created from terminal has changed. No resolution is set. We highly recommend to use config.ini files which you can set to `create_avd_hardware_config_filepath` or specify resolution in `create_avd_additional_options`. 78 | - all fields need to be present in JSON, if you don't need any of the parameters just leave it empty 79 | - pick even ports for your AVDs 80 | - if you can't launch more than 2 AVDs be sure to check how much memory is your HAXM allowed to use (by default it is 2GB and that will allow you to launch around 2 AVDs) If you face any problems with freezing AVDs then be sure to reinstall your HAXM and allow it to use more of RAM (https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager) 81 | - make sure you have all targets/abis installed on your PC if you want to use them (type in terminal: `android list targets`) 82 | - we recommend adding `-gpu on` to your launching options for each device, it helps when working with many AVDs 83 | 84 | Example: 85 | 86 | [Example of complete JSON file can be found here.](fastlane/examples/AVD_setup.json) 87 | 88 | ## Issues and Feedback 89 | 90 | For any other issues and feedback about this plugin, please submit it to this repository. 91 | 92 | ## Troubleshooting 93 | 94 | If you have trouble using plugins, check out the [Plugins Troubleshooting](https://github.com/fastlane/fastlane/blob/master/fastlane/docs/PluginsTroubleshooting.md) doc in the main `fastlane` repo. 95 | 96 | ## Using `fastlane` Plugins 97 | 98 | For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Plugins.md). 99 | 100 | ## About `fastlane` 101 | 102 | `fastlane` is the easiest way to automate building and releasing your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools). 103 | 104 | 105 | # Towards financial services available to all 106 | We’re working throughout the company to create faster, cheaper, and more available financial services all over the world, and here are some of the techniques that we’re utilizing. There’s still a long way ahead of us, and if you’d like to be part of that journey, check out our [careers page](https://bit.ly/3vajnu6). 107 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new 5 | 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new(:rubocop) 8 | 9 | task default: [:spec, :rubocop] 10 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | override: 3 | - bundle exec rake 4 | machine: 5 | ruby: 6 | version: 2.2.4 7 | # Enable xcode below if you need macOS 8 | # xcode: 9 | # version: "7.3" 10 | -------------------------------------------------------------------------------- /fastlane-plugin-automated_test_emulator_run.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'fastlane/plugin/automated_test_emulator_run/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'fastlane-plugin-automated_test_emulator_run' 8 | spec.version = Fastlane::AutomatedTestEmulatorRun::VERSION 9 | spec.author = %q{Kamil Krzyk} 10 | spec.email = %q{krzyk.kamil@gmail.com} 11 | 12 | spec.summary = %q{Starts n AVDs based on JSON file config. AVDs are created and configured according to user liking before instrumentation test process (started either via shell command or gradle) and killed/deleted after test process finishes.} 13 | spec.homepage = "https://github.com/AzimoLabs/fastlane-plugin-automated-test-emulator-run" 14 | spec.license = "MIT" 15 | 16 | spec.files = Dir["lib/**/*"] + %w(README.md LICENSE) 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ['lib'] 19 | 20 | spec.add_development_dependency 'pry' 21 | spec.add_development_dependency 'bundler' 22 | spec.add_development_dependency 'rspec' 23 | spec.add_development_dependency 'rake' 24 | spec.add_development_dependency 'rubocop' 25 | spec.add_development_dependency 'fastlane', '>= 1.98.0' 26 | end 27 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | lane :test do 2 | 3 | desc "Lane running all tests with default spoon config" 4 | lane :Automation_Spoon do 5 | gradle(task: "spoon") 6 | end 7 | 8 | desc "Runs tests with AVD setup according to JSON file config with usage of spoon." 9 | lane :Automation_EmulatorRun_Spoon do 10 | automated_test_emulator_run( 11 | AVD_setup_path: "~//AVD_setup.json", 12 | gradle_task:"spoon" 13 | ) 14 | end 15 | 16 | desc "Runs tests with AVD setup according to JSON file config with usage of gradle task." 17 | lane :Automation_EmulatorRun_Gradle do 18 | automated_test_emulator_run( 19 | AVD_setup_path: "~//AVD_setup.json", 20 | gradle_task:"cleanTest createMockDebugCoverageReport --continue" 21 | ) 22 | end 23 | 24 | desc "Runs tests with AVD setup according to JSON file config with usage of other lane." 25 | lane :Automation_EmulatorRun_Fastlane do 26 | automated_test_emulator_run( 27 | AVD_setup_path: "~//AVD_setup.json", 28 | shell_task:"fastlane Automation_Spoon" 29 | ) 30 | end 31 | 32 | # more parameters listed in automated_test_emulator_run_action.rb 33 | end 34 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | -------------------------------------------------------------------------------- /fastlane/examples/AVD_setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "avd_list": [ 3 | { 4 | "avd_name": "Test-Emulator-API23-Nexus-5-1", 5 | 6 | "create_avd_package": "system-images;android-23;google_apis;x86_64", 7 | "create_avd_device": "Nexus 5X", 8 | "create_avd_tag": "google_apis", 9 | "create_avd_abi": "x86_64", 10 | "create_avd_additional_options": "", 11 | "create_avd_hardware_config_filepath": "~/Android/AVD_Snapshots/Nexus_5X_API_23/Test-Emulator-API23-Nexus-5-1.ini", 12 | 13 | "launch_avd_port": "", 14 | "launch_avd_snapshot_filepath": "~/Android/AVD_Snapshots/Nexus_5X_API_23/Nexus_5X_API_23_SNAPSHOT.img", 15 | "launch_avd_launch_binary_name": "emulator", 16 | "launch_avd_additional_options": "-gpu on" 17 | }, 18 | { 19 | "avd_name": "Test-Emulator-API23-Nexus-5-2", 20 | 21 | "create_avd_package": "system-images;android-23;google_apis;x86_64", 22 | "create_avd_device": "Nexus 5X", 23 | "create_avd_tag": "google_apis", 24 | "create_avd_abi": "x86_64", 25 | "create_avd_additional_options": "", 26 | "create_avd_hardware_config_filepath": "~/Android/AVD_Snapshots/Nexus_5X_API_23/Test-Emulator-API23-Nexus-5-2.ini", 27 | 28 | "launch_avd_port": "", 29 | "launch_avd_snapshot_filepath": "~/Android/AVD_Snapshots/Nexus_5X_API_23/Nexus_5X_API_23_SNAPSHOT.img", 30 | "launch_avd_launch_binary_name": "emulator", 31 | "launch_avd_additional_options": "-gpu on" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/automated_test_emulator_run.rb: -------------------------------------------------------------------------------- 1 | require 'fastlane/plugin/automated_test_emulator_run/version' 2 | 3 | module Fastlane 4 | module AutomatedTestEmulatorRun 5 | def self.all_classes 6 | Dir[File.expand_path('**/{actions,factory,provider}/*.rb', File.dirname(__FILE__))] 7 | end 8 | end 9 | end 10 | 11 | Fastlane::AutomatedTestEmulatorRun.all_classes.each do |current| 12 | require current 13 | end 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/automated_test_emulator_run/actions/automated_test_emulator_run_action.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | require 'json' 3 | 4 | module Fastlane 5 | module Actions 6 | 7 | class AutomatedTestEmulatorRunAction < Action 8 | def self.run(params) 9 | UI.message("The automated_test_emulator_run plugin is working!") 10 | 11 | # Parse JSON with AVD launch confing to array of AVD_scheme objects 12 | avd_schemes = Provider::AvdSchemeProvider.get_avd_schemes(params) 13 | 14 | # ADB, AVD helper classes 15 | adb_controller = Factory::AdbControllerFactory.get_adb_controller(params) 16 | avd_controllers = [] 17 | 18 | # Create AVD_Controller class for each AVD_scheme 19 | for i in 0...avd_schemes.length 20 | avd_controller = Factory::AvdControllerFactory.get_avd_controller(params, avd_schemes[i]) 21 | avd_controllers << avd_controller 22 | 23 | if params[:verbose] 24 | # Checking for output files 25 | if File.exists?(avd_controller.output_file.path) 26 | UI.message([ 27 | "Successfully created tmp output file for AVD:", 28 | avd_schemes[i].avd_name + ".", 29 | "File: " + avd_controller.output_file.path].join(" ").green) 30 | else 31 | UI.message([ 32 | "Unable to create output file for AVD:", 33 | avd_schemes[i].avd_name + ".", 34 | "Output will be delegated to null and lost. Check your save/read permissions."].join(" ").red) 35 | end 36 | end 37 | end 38 | 39 | # Reseting wait states 40 | all_avd_launched = false 41 | adb_launch_complete = false 42 | param_launch_complete = false 43 | 44 | while(!all_avd_launched) 45 | # Preparation 46 | UI.message("Configuring environment in order to launch emulators: ".yellow) 47 | UI.message("Getting avaliable AVDs".yellow) 48 | devices = Action.sh(adb_controller.command_get_avds) 49 | 50 | for i in 0...avd_schemes.length 51 | unless devices.match(avd_schemes[i].avd_name).nil? 52 | UI.message(["AVD with name '", avd_schemes[i].avd_name, "' currently exists."].join("").yellow) 53 | if params[:AVD_recreate_new] 54 | # Delete existing AVDs 55 | UI.message("AVD_create_new parameter set to true.".yellow) 56 | UI.message(["Deleting existing AVD with name:", avd_schemes[i].avd_name].join(" ").yellow) 57 | Action.sh(avd_controllers[i].command_delete_avd) 58 | 59 | # Re-create AVD 60 | UI.message(["Re-creating new AVD."].join(" ").yellow) 61 | Action.sh(avd_controllers[i].command_create_avd) 62 | else 63 | # Use existing AVD 64 | UI.message("AVD_recreate_new parameter set to false.".yellow) 65 | UI.message("Using existing AVD for tests.".yellow) 66 | end 67 | else 68 | # Create AVD 69 | UI.message(["AVD with name '", avd_schemes[i].avd_name, "' does not exist. Creating new AVD."].join("").yellow) 70 | Action.sh(avd_controllers[i].command_create_avd) 71 | end 72 | end 73 | 74 | # Restart ADB 75 | if params[:ADB_restart] 76 | UI.message("Restarting adb".yellow) 77 | Action.sh(adb_controller.command_stop) 78 | Action.sh(adb_controller.command_start) 79 | else 80 | UI.message("ADB won't be restarted. 'ADB_restart' set to false.".yellow) 81 | end 82 | 83 | # Applying custom configs (it's not done directly after create because 'cat' operation seems to fail overwrite) 84 | for i in 0...avd_schemes.length 85 | UI.message(["Attemting to apply custom config to ", avd_schemes[i].avd_name].join("").yellow) 86 | if avd_controllers[i].command_apply_config_avd.eql? "" 87 | UI.message(["No config file found for AVD '", avd_schemes[i].avd_name, "'. AVD won't have config.ini applied."].join("").yellow) 88 | else 89 | UI.message(["Config file found! Applying custom config to: ", avd_schemes[i].avd_name].join("").yellow) 90 | Action.sh(avd_controllers[i].command_apply_config_avd) 91 | end 92 | end 93 | 94 | # Launching AVDs 95 | UI.message("Launching all AVDs at the same time.".yellow) 96 | for i in 0...avd_controllers.length 97 | Process.fork do 98 | Action.sh(avd_controllers[i].command_start_avd) 99 | end 100 | end 101 | 102 | # Wait for AVDs finish booting 103 | UI.message("Waiting for AVDs to finish booting.".yellow) 104 | UI.message("Performing wait for ADB boot".yellow) 105 | adb_launch_complete = wait_for_emulator_boot_by_adb(adb_controller, avd_schemes, "#{params[:AVD_adb_launch_timeout]}") 106 | 107 | # Wait for AVD params finish booting 108 | if adb_launch_complete 109 | UI.message("Wait for ADB boot completed with success".yellow) 110 | 111 | if (params[:AVD_wait_for_bootcomplete] || params[:AVD_wait_for_boot_completed] || params[:AVD_wait_for_bootanim]) 112 | message = "Performing wait for params: " 113 | 114 | if params[:AVD_wait_for_bootcomplete] 115 | message += "'dev.bootcomplete', " 116 | end 117 | 118 | if params[:AVD_wait_for_boot_completed] 119 | message += "'sys.boot_completed', " 120 | end 121 | 122 | if params[:AVD_wait_for_bootanim] 123 | message += "'init.svc.bootanim', " 124 | end 125 | 126 | message = message[0...-2] + "." 127 | UI.message(message.yellow) 128 | 129 | param_launch_complete = wait_for_emulator_boot_by_params(params, adb_controller, avd_controllers, avd_schemes, "#{params[:AVD_param_launch_timeout]}") 130 | else 131 | UI.message("Wait for AVD launch params was turned off. Skipping...".yellow) 132 | param_launch_complete = true 133 | end 134 | else 135 | UI.message("Wait for ADB boot failed".yellow) 136 | end 137 | 138 | all_avd_launched = adb_launch_complete && param_launch_complete 139 | 140 | # Deciding if AVD launch should be restarted 141 | devices_output = Action.sh(adb_controller.command_get_devices) 142 | 143 | devices = "" 144 | devices_output.each_line do |line| 145 | if line.include?("emulator-") 146 | devices += line.sub(/\t/, " ") 147 | end 148 | end 149 | 150 | if all_avd_launched 151 | UI.message("AVDs Booted!".green) 152 | if params[:logcat] 153 | for i in 0...avd_schemes.length 154 | device = ["emulator-", avd_schemes[i].launch_avd_port].join('') 155 | cmd = [adb_controller.adb_path, '-s', device, 'logcat -c'].join(' ') 156 | Action.sh(cmd) unless devices.match(device).nil? 157 | end 158 | end 159 | else 160 | for i in 0...avd_schemes.length 161 | if params[:verbose] 162 | # Display AVD output 163 | if (File.exists?(avd_controllers[i].output_file.path)) 164 | UI.message(["Displaying log for AVD:", avd_schemes[i].avd_name].join(" ").red) 165 | UI.message(avd_controllers[i].output_file.read.blue) 166 | end 167 | end 168 | 169 | # Killing devices 170 | unless devices.match(["emulator-", avd_schemes[i].launch_avd_port].join("")).nil? 171 | Action.sh(avd_controllers[i].command_kill_device) 172 | end 173 | end 174 | end 175 | end 176 | 177 | # Launching tests 178 | shell_task = "#{params[:shell_task]}" unless params[:shell_task].nil? 179 | gradle_task = "#{params[:gradle_task]}" unless params[:gradle_task].nil? 180 | 181 | UI.message("Starting tests".green) 182 | begin 183 | unless shell_task.nil? 184 | UI.message("Using shell task.".green) 185 | Action.sh(shell_task) 186 | end 187 | 188 | unless gradle_task.nil? 189 | gradle = Helper::GradleHelper.new(gradle_path: Dir["./gradlew"].last) 190 | 191 | UI.message("Using gradle task.".green) 192 | gradle.trigger(task: params[:gradle_task], flags: params[:gradle_flags], serial: nil) 193 | end 194 | ensure 195 | # Clean up 196 | for i in 0...avd_schemes.length 197 | # Kill all emulators 198 | device = ["emulator-", avd_schemes[i].launch_avd_port].join("") 199 | unless devices.match(device).nil? 200 | if params[:logcat] 201 | file = [device, '.log'].join('') 202 | cmd = [adb_controller.adb_path, '-s', device, 'logcat -d >', file].join(' ') 203 | Action.sh(cmd) 204 | end 205 | Action.sh(avd_controllers[i].command_kill_device) 206 | end 207 | 208 | if params[:verbose] 209 | # Display AVD output 210 | if (File.exists?(avd_controllers[i].output_file.path)) 211 | UI.message("Displaying log from AVD to console:".green) 212 | UI.message(avd_controllers[i].output_file.read.blue) 213 | 214 | UI.message("Removing temp file.".green) 215 | avd_controllers[i].output_file.close 216 | avd_controllers[i].output_file.unlink 217 | end 218 | end 219 | 220 | # Delete AVDs 221 | if params[:AVD_clean_after] 222 | UI.message("AVD_clean_after param set to true. Deleting AVDs.".green) 223 | Action.sh(avd_controllers[i].command_delete_avd) 224 | else 225 | UI.message("AVD_clean_after param set to false. Created AVDs won't be deleted.".green) 226 | end 227 | end 228 | end 229 | end 230 | 231 | def self.wait_for_emulator_boot_by_adb(adb_controller, avd_schemes, timeout) 232 | timeoutInSeconds= timeout.to_i 233 | interval = 1000 * 10 234 | startTime = Time.now 235 | lastCheckTime = Time.now 236 | launch_status_hash = Hash.new 237 | device_visibility_hash = Hash.new 238 | 239 | for i in 0...avd_schemes.length 240 | device_name = ["emulator-", avd_schemes[i].launch_avd_port].join("") 241 | launch_status_hash.store(device_name, false) 242 | device_visibility_hash.store(device_name, false) 243 | end 244 | 245 | launch_status = false 246 | loop do 247 | currentTime = Time.now 248 | if ((currentTime - lastCheckTime) * 1000) > interval 249 | lastCheckTime = currentTime 250 | devices_output = Action.sh(adb_controller.command_get_devices) 251 | 252 | devices = "" 253 | devices_output.each_line do |line| 254 | if line.include?("emulator-") 255 | devices += line.sub(/\t/, " ") 256 | end 257 | end 258 | 259 | # Check if device is visible 260 | all_devices_visible = true 261 | device_visibility_hash.each do |name, is_visible| 262 | unless (devices.match(name).nil? || is_visible) 263 | device_visibility_hash[name] = true 264 | end 265 | all_devices_visible = false unless is_visible 266 | end 267 | 268 | # Check if device is booted 269 | all_devices_booted = true 270 | launch_status_hash.each do |name, is_booted| 271 | unless (devices.match(name + " device").nil? || is_booted) 272 | launch_status_hash[name] = true 273 | end 274 | all_devices_booted = false unless launch_status_hash[name] 275 | end 276 | 277 | # Quit if timeout reached 278 | if ((currentTime - startTime) >= timeoutInSeconds) 279 | UI.message(["AVD ADB loading took more than ", timeout, ". Attempting to re-launch."].join("").red) 280 | launch_status = false 281 | break 282 | end 283 | 284 | # Quit if all devices booted 285 | if (all_devices_booted && all_devices_visible) 286 | launch_status = true 287 | break 288 | end 289 | end 290 | end 291 | return launch_status 292 | end 293 | 294 | def self.wait_for_emulator_boot_by_params(params, adb_controller, avd_controllers, avd_schemes, timeout) 295 | timeout_in_seconds= timeout.to_i 296 | interval = 1000 * 10 297 | all_params_launched = false 298 | start_time = last_scan_ended = Time.now 299 | device_boot_statuses = Hash.new 300 | 301 | loop do 302 | current_time = Time.now 303 | 304 | # Performing single scan over each device 305 | if (((current_time - last_scan_ended) * 1000) >= interval || start_time == last_scan_ended) 306 | for i in 0...avd_schemes.length 307 | avd_schema = avd_schemes[i] 308 | avd_controller = avd_controllers[i] 309 | avd_param_boot_hash = Hash.new 310 | avd_param_status_hash = Hash.new 311 | avd_booted = false 312 | 313 | # Retreiving device parameters according to config 314 | if params[:AVD_wait_for_bootcomplete] 315 | dev_bootcomplete, _stdeerr, _status = Open3.capture3([avd_controller.command_get_property, "dev.bootcomplete"].join(" ")) 316 | avd_param_boot_hash.store("dev.bootcomplete", dev_bootcomplete.strip.eql?("1")) 317 | avd_param_status_hash.store("dev.bootcomplete", dev_bootcomplete) 318 | end 319 | 320 | if params[:AVD_wait_for_boot_completed] 321 | sys_boot_completed, _stdeerr, _status = Open3.capture3([avd_controller.command_get_property, "sys.boot_completed"].join(" ")) 322 | avd_param_boot_hash.store("sys.boot_completed", sys_boot_completed.strip.eql?("1")) 323 | avd_param_status_hash.store("sys.boot_completed", sys_boot_completed) 324 | end 325 | 326 | if params[:AVD_wait_for_bootanim] 327 | bootanim, _stdeerr, _status = Open3.capture3([avd_controller.command_get_property, "init.svc.bootanim"].join(" ")) 328 | avd_param_boot_hash.store("init.svc.bootanim", bootanim.strip.eql?("stopped")) 329 | avd_param_status_hash.store("init.svc.bootanim", bootanim) 330 | end 331 | 332 | # Checking for param statuses 333 | avd_param_boot_hash.each do |name, is_booted| 334 | if !is_booted 335 | break 336 | end 337 | avd_booted = true 338 | end 339 | device_boot_statuses.store(avd_schema.avd_name, avd_booted) 340 | 341 | # Plotting current wait results 342 | device_log = "Device 'emulator-" + avd_schemes[i].launch_avd_port.to_s + "' launch status:" 343 | UI.message(device_log.magenta) 344 | avd_param_boot_hash.each do |name, is_booted| 345 | device_log = "'" + name + "' - '" + avd_param_status_hash[name].strip + "' (launched: " + is_booted.to_s + ")" 346 | UI.message(device_log.magenta) 347 | end 348 | end 349 | last_scan_ended = Time.now 350 | end 351 | 352 | # Checking if wait doesn't last too long 353 | if (current_time - start_time) >= timeout_in_seconds 354 | UI.message(["AVD param loading took more than ", timeout, ". Attempting to re-launch."].join("").red) 355 | all_params_launched = false 356 | break 357 | end 358 | 359 | # Finishing wait with success if all params are loaded for every device 360 | device_boot_statuses.each do |name, is_booted| 361 | if !is_booted 362 | break 363 | end 364 | all_params_launched = true 365 | end 366 | if all_params_launched 367 | break 368 | end 369 | end 370 | return all_params_launched 371 | end 372 | 373 | def self.available_options 374 | [ 375 | #paths 376 | FastlaneCore::ConfigItem.new(key: :AVD_path, 377 | env_name: "AVD_PATH", 378 | description: "The path to your android AVD directory (root). ANDROID_SDK_HOME by default", 379 | default_value: (ENV['ANDROID_SDK_HOME'].nil? or ENV['ANDROID_SDK_HOME'].eql?("")) ? "~/.android/avd" : ENV['ANDROID_SDK_HOME'], 380 | is_string: true, 381 | optional: true), 382 | FastlaneCore::ConfigItem.new(key: :AVD_setup_path, 383 | env_name: "AVD_SETUP_PATH", 384 | description: "Location to AVD_setup.json file which contains info about how many AVD should be launched and their configs", 385 | is_string: true, 386 | optional: false), 387 | FastlaneCore::ConfigItem.new(key: :SDK_path, 388 | env_name: "SDK_PATH", 389 | description: "The path to your android sdk directory (root). ANDROID_HOME by default", 390 | default_value: ENV['ANDROID_HOME'], 391 | is_string: true, 392 | optional: true), 393 | 394 | 395 | #launch config params 396 | FastlaneCore::ConfigItem.new(key: :AVD_param_launch_timeout, 397 | env_name: "AVD_PARAM_LAUNCH_TIMEOUT", 398 | description: "Timeout in seconds. Even though ADB might find all devices you still might want to wait for animations to finish and system to boot. Default 60 seconds", 399 | default_value: 60, 400 | is_string: true, 401 | optional: true), 402 | FastlaneCore::ConfigItem.new(key: :AVD_adb_launch_timeout, 403 | env_name: "AVD_ADB_LAUNCH_TIMEOUT", 404 | description: "Timeout in seconds. Wait until ADB finds all devices specified in config and sets their value to 'device'. Default 240 seconds", 405 | default_value: 240, 406 | is_string: true, 407 | optional: true), 408 | FastlaneCore::ConfigItem.new(key: :AVD_recreate_new, 409 | env_name: "AVD_RECREATE_NEW", 410 | description: "Allow to decide if AVDs from AVD_setup.json (in case they already exist) should be deleted and created from scratch", 411 | default_value: true, 412 | is_string: false, 413 | optional: true), 414 | FastlaneCore::ConfigItem.new(key: :AVD_clean_after, 415 | env_name: "AVD_CLEAN_AFTER", 416 | description: "Allow to decide if AVDs should be deleted from PC after test session ends", 417 | default_value: true, 418 | is_string: false, 419 | optional: true), 420 | FastlaneCore::ConfigItem.new(key: :ADB_restart, 421 | env_name: "ADB_RESTART", 422 | description: "Allows to switch adb restarting on/off", 423 | default_value: true, 424 | is_string: false, 425 | optional: true), 426 | FastlaneCore::ConfigItem.new(key: :AVD_wait_for_bootcomplete, 427 | env_name: "AVD_BOOTCOMPLETE_WAIT", 428 | description: "Allows to switch wait for 'dev.bootcomplete' AVD launch param on/off", 429 | default_value: true, 430 | is_string: false, 431 | optional: true), 432 | FastlaneCore::ConfigItem.new(key: :AVD_wait_for_boot_completed, 433 | env_name: "AVD_BOOT_COMPLETED_WAIT", 434 | description: "Allows to switch wait for 'sys.boot_completed' AVD launch param on/off", 435 | default_value: true, 436 | is_string: false, 437 | optional: true), 438 | FastlaneCore::ConfigItem.new(key: :AVD_wait_for_bootanim, 439 | env_name: "ABD_BOOTANIM_WAIT", 440 | description: "Allows to switch wait for 'init.svc.bootanim' AVD launch param on/off", 441 | default_value: true, 442 | is_string: false, 443 | optional: true), 444 | 445 | #launch commands 446 | FastlaneCore::ConfigItem.new(key: :shell_task, 447 | env_name: "SHELL_TASK", 448 | description: "The shell command you want to execute", 449 | conflicting_options: [:gradle_task], 450 | is_string: true, 451 | optional: true), 452 | FastlaneCore::ConfigItem.new(key: :gradle_task, 453 | env_name: "GRADLE_TASK", 454 | description: "The gradle task you want to execute", 455 | conflicting_options: [:shell_command], 456 | is_string: true, 457 | optional: true), 458 | FastlaneCore::ConfigItem.new(key: :gradle_flags, 459 | env_name: "GRADLE_FLAGS", 460 | description: "All parameter flags you want to pass to the gradle command, e.g. `--exitcode --xml file.xml`", 461 | conflicting_options: [:shell_command], 462 | optional: true, 463 | is_string: true), 464 | 465 | #mode 466 | FastlaneCore::ConfigItem.new(key: :verbose, 467 | env_name: "AVD_VERBOSE", 468 | description: "Allows to turn on/off mode verbose which displays output of AVDs", 469 | default_value: false, 470 | is_string: false, 471 | optional: true), 472 | FastlaneCore::ConfigItem.new(key: :logcat, 473 | env_name: "ADB_LOGCAT", 474 | description: "Allows to turn logcat on/off so you can debug crashes and such", 475 | default_value: false, 476 | is_string: false, 477 | optional: true), 478 | ] 479 | end 480 | 481 | def self.description 482 | "Starts AVD, based on AVD_setup.json file, before test launch and kills it after testing is done." 483 | end 484 | 485 | def self.authors 486 | ["F1sherKK"] 487 | end 488 | 489 | def self.is_supported?(platform) 490 | platform == :android 491 | end 492 | end 493 | end 494 | end 495 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/automated_test_emulator_run/factory/adb_controller_factory.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Factory 3 | 4 | class ADB_Controller 5 | attr_accessor :command_stop, 6 | :command_start, 7 | :command_get_devices, 8 | :command_wait_for_device, 9 | :command_get_avds, 10 | :adb_path 11 | end 12 | 13 | class AdbControllerFactory 14 | 15 | def self.get_adb_controller(params) 16 | UI.message(["Preparing commands for Android ADB"].join(" ").yellow) 17 | 18 | # Get paths 19 | path_sdk = "#{params[:SDK_path]}" 20 | path_avdmanager_binary = path_sdk + "/tools/bin/avdmanager" 21 | path_adb = path_sdk + "/platform-tools/adb" 22 | 23 | # ADB shell command parts 24 | sh_stop_adb = "kill-server" 25 | sh_start_adb = "start-server" 26 | sh_devices_adb = "devices" 27 | sh_wait_for_device_adb = "wait-for-device" 28 | sh_list_avd_adb = "list avd" 29 | 30 | # Assemble ADB controller 31 | adb_controller = ADB_Controller.new 32 | adb_controller.command_stop = [ 33 | path_adb, 34 | sh_stop_adb 35 | ].join(" ") 36 | 37 | adb_controller.command_start = [ 38 | path_adb, 39 | sh_start_adb 40 | ].join(" ") 41 | 42 | adb_controller.command_get_devices = [ 43 | path_adb, 44 | sh_devices_adb 45 | ].join(" ") 46 | 47 | adb_controller.command_wait_for_device = [ 48 | path_adb, 49 | sh_wait_for_device_adb 50 | ].join(" ") 51 | 52 | adb_controller.adb_path = path_adb 53 | 54 | adb_controller.command_get_avds = [ 55 | path_avdmanager_binary, 56 | sh_list_avd_adb].join(" ").chomp 57 | 58 | return adb_controller 59 | end 60 | end 61 | end 62 | end -------------------------------------------------------------------------------- /lib/fastlane/plugin/automated_test_emulator_run/factory/avd_controller_factory.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | module Fastlane 4 | module Factory 5 | 6 | class AVD_Controller 7 | attr_accessor :command_create_avd, :command_start_avd, :command_delete_avd, :command_apply_config_avd, :command_get_property, :command_kill_device, 8 | :output_file 9 | 10 | def self.create_output_file(params) 11 | output_file = Tempfile.new('emulator_output', '#{params[:AVD_path]}') 12 | end 13 | end 14 | 15 | class AvdControllerFactory 16 | 17 | def self.get_avd_controller(params, avd_scheme) 18 | UI.message(["Preparing parameters and commands for emulator:", avd_scheme.avd_name].join(" ").yellow) 19 | 20 | # Get paths 21 | path_sdk = "#{params[:SDK_path]}" 22 | path_avdmanager_binary = path_sdk + "/tools/bin/avdmanager" 23 | path_adb = path_sdk + "/platform-tools/adb" 24 | path_avd = "#{params[:AVD_path]}" 25 | 26 | # Create AVD shell command parts 27 | sh_create_answer_no = "echo \"no\" |" 28 | sh_create_avd = "create avd" 29 | sh_create_avd_name = ["--name \"", avd_scheme.avd_name, "\""].join("") 30 | sh_create_avd_package = ["--package \"", avd_scheme.create_avd_package, "\""].join("") 31 | 32 | if avd_scheme.create_avd_device.eql? "" 33 | sh_create_avd_device = "" 34 | else 35 | sh_create_avd_device = ["--device \"", avd_scheme.create_avd_device, "\""].join("") 36 | end 37 | 38 | if avd_scheme.create_avd_abi.eql? "" 39 | sh_create_avd_abi = "" 40 | else 41 | sh_create_avd_abi = ["--abi ", avd_scheme.create_avd_abi].join("") 42 | end 43 | 44 | if avd_scheme.create_avd_tag.eql? "" 45 | sh_create_avd_tag = "" 46 | else 47 | sh_create_avd_tag = ["--tag ", avd_scheme.create_avd_tag].join("") 48 | end 49 | 50 | sh_create_avd_additional_options = avd_scheme.create_avd_additional_options 51 | sh_create_config_loc = "#{path_avd}/#{avd_scheme.avd_name}.avd/config.ini" 52 | 53 | # Launch AVD shell command parts 54 | sh_launch_emulator_binary = [path_sdk, "/emulator/", avd_scheme.launch_avd_launch_binary_name].join("") 55 | sh_launch_avd_name = ["-avd ", avd_scheme.avd_name].join("") 56 | sh_launch_avd_additional_options = avd_scheme.launch_avd_additional_options 57 | sh_launch_avd_port = ["-port", avd_scheme.launch_avd_port].join(" ") 58 | 59 | if avd_scheme.launch_avd_snapshot_filepath.eql? "" 60 | sh_launch_avd_snapshot = "" 61 | else 62 | sh_launch_avd_snapshot = ["-wipe-data -initdata ", avd_scheme.launch_avd_snapshot_filepath].join("") 63 | end 64 | 65 | # Re-create AVD shell command parts 66 | sh_delete_avd = ["delete avd -n ", avd_scheme.avd_name].join("") 67 | 68 | # ADB related shell command parts 69 | sh_specific_device = "-s" 70 | sh_device_name_adb = ["emulator-", avd_scheme.launch_avd_port].join("") 71 | sh_get_property = "shell getprop" 72 | sh_kill_device = "emu kill" 73 | 74 | # Assemble AVD controller 75 | avd_controller = AVD_Controller.new 76 | avd_controller.command_create_avd = [ 77 | sh_create_answer_no, 78 | path_avdmanager_binary, 79 | sh_create_avd, 80 | sh_create_avd_name, 81 | sh_create_avd_package, 82 | sh_create_avd_device, 83 | sh_create_avd_tag, 84 | sh_create_avd_abi, 85 | sh_create_avd_additional_options].join(" ") 86 | 87 | avd_controller.output_file = Tempfile.new('emulator_output') 88 | avd_output = File.exists?(avd_controller.output_file) ? ["&>", avd_controller.output_file.path, "&"].join("") : "&>/dev/null &" 89 | 90 | avd_controller.command_start_avd = [ 91 | sh_launch_emulator_binary, 92 | sh_launch_avd_port, 93 | sh_launch_avd_name, 94 | sh_launch_avd_snapshot, 95 | sh_launch_avd_additional_options, 96 | avd_output].join(" ") 97 | 98 | avd_controller.command_delete_avd = [ 99 | path_avdmanager_binary, 100 | sh_delete_avd].join(" ") 101 | 102 | if path_avd.nil? || (avd_scheme.create_avd_hardware_config_filepath.eql? "") 103 | avd_controller.command_apply_config_avd = "" 104 | else 105 | avd_controller.command_apply_config_avd = [ 106 | "cat", 107 | avd_scheme.create_avd_hardware_config_filepath, 108 | ">", 109 | sh_create_config_loc].join(" ") 110 | end 111 | 112 | avd_controller.command_get_property = [ 113 | path_adb, 114 | sh_specific_device, 115 | sh_device_name_adb, 116 | sh_get_property].join(" ") 117 | 118 | avd_controller.command_kill_device = [ 119 | path_adb, 120 | sh_specific_device, 121 | sh_device_name_adb, 122 | sh_kill_device, 123 | "&>/dev/null"].join(" ") 124 | 125 | return avd_controller 126 | end 127 | end 128 | end 129 | end -------------------------------------------------------------------------------- /lib/fastlane/plugin/automated_test_emulator_run/provider/avd_setup_provider.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Provider 3 | 4 | class AVD_scheme 5 | attr_accessor :avd_name, :create_avd_package, :create_avd_device, :create_avd_tag, :create_avd_abi, :create_avd_hardware_config_filepath, :create_avd_additional_options, 6 | :launch_avd_port, :launch_avd_launch_binary_name, :launch_avd_additional_options, :launch_avd_snapshot_filepath 7 | end 8 | 9 | class AvdSchemeProvider 10 | 11 | def self.get_avd_schemes(params) 12 | 13 | # Read JSON into string variable 14 | avd_setup_json = read_avd_setup(params) 15 | if avd_setup_json.nil? 16 | throw_error("Unable to read AVD_setup.json. Check JSON file structure or file path.") 17 | end 18 | 19 | # Read JSON into Hash 20 | avd_setup = JSON.parse(avd_setup_json) 21 | avd_hash_list = avd_setup['avd_list'] 22 | 23 | # Create AVD_scheme objects and fill them with data 24 | avd_scheme_list = [] 25 | for i in 0...avd_hash_list.length 26 | avd_hash = avd_hash_list[i] 27 | 28 | avd_scheme = AVD_scheme.new 29 | avd_scheme.avd_name = avd_hash['avd_name'] 30 | 31 | avd_scheme.create_avd_package = avd_hash['create_avd_package'] 32 | avd_scheme.create_avd_device = avd_hash['create_avd_device'] 33 | avd_scheme.create_avd_tag = avd_hash['create_avd_tag'] 34 | avd_scheme.create_avd_abi = avd_hash['create_avd_abi'] 35 | avd_scheme.create_avd_hardware_config_filepath = avd_hash['create_avd_hardware_config_filepath'] 36 | 37 | avd_scheme.launch_avd_port = avd_hash['launch_avd_port'] 38 | avd_scheme.launch_avd_launch_binary_name = avd_hash['launch_avd_launch_binary_name'] 39 | avd_scheme.launch_avd_additional_options = avd_hash['launch_avd_additional_options'] 40 | avd_scheme.launch_avd_snapshot_filepath = avd_hash['launch_avd_snapshot_filepath'] 41 | 42 | errors = check_avd_fields(avd_scheme) 43 | unless errors.empty? 44 | error_log = "Error! Fields not found in JSON: \n" 45 | errors.each { |error| error_log += error + "\n" } 46 | throw_error(error_log) 47 | end 48 | 49 | avd_scheme_list << avd_scheme 50 | end 51 | 52 | # Prepare list of open ports for AVD_schemes without ports set in JSON 53 | avaliable_ports = get_unused_even_tcp_ports(5556, 5586, avd_scheme_list) 54 | 55 | # Fill empty AVD_schemes with open ports 56 | for i in 0...avd_scheme_list.length 57 | avd_scheme = avd_scheme_list[i] 58 | if avd_scheme.launch_avd_port.eql? "" 59 | avd_scheme.launch_avd_port = avaliable_ports[0] 60 | avaliable_ports.delete(avaliable_ports[0]) 61 | end 62 | end 63 | 64 | return avd_scheme_list 65 | end 66 | 67 | def self.get_unused_even_tcp_ports(min_port, max_port, avd_scheme_list) 68 | if min_port % 2 != 0 69 | min_port += 1 70 | end 71 | 72 | if max_port % 2 != 0 73 | max_port += 1 74 | end 75 | 76 | avaliable_ports = [] 77 | reserved_ports = [] 78 | 79 | # Gather ports requested in JSON config 80 | for i in 0...avd_scheme_list.length 81 | avd_scheme = avd_scheme_list[i] 82 | unless avd_scheme.launch_avd_port.eql? "" 83 | reserved_ports << avd_scheme.launch_avd_port 84 | end 85 | end 86 | 87 | # Find next open port which wasn't reserved in JSON config 88 | port = min_port 89 | for i in 0...avd_scheme_list.length 90 | 91 | while port < max_port do 92 | if !system("lsof -i:#{port}", out: '/dev/null') 93 | 94 | is_port_reserved = false 95 | for j in 0...reserved_ports.length 96 | if reserved_ports[j].eql?(port.to_s) 97 | is_port_reserved = true 98 | break 99 | end 100 | end 101 | 102 | if is_port_reserved 103 | port = port + 2 104 | break 105 | end 106 | 107 | avaliable_ports << port 108 | port = port + 2 109 | break 110 | else 111 | port = port + 2 112 | end 113 | end 114 | end 115 | 116 | return avaliable_ports 117 | end 118 | 119 | def self.read_avd_setup(params) 120 | if File.exists?(File.expand_path("#{params[:AVD_setup_path]}")) 121 | file = File.open(File.expand_path("#{params[:AVD_setup_path]}"), "rb") 122 | json = file.read 123 | file.close 124 | return json 125 | else 126 | return nil 127 | end 128 | end 129 | 130 | def self.check_avd_fields(avd_scheme) 131 | errors = [] 132 | 133 | if avd_scheme.avd_name.nil? 134 | errors.push("avd_name not found") 135 | end 136 | if avd_scheme.create_avd_package.nil? 137 | errors.push("create_avd_package not found") 138 | end 139 | if avd_scheme.create_avd_device.nil? 140 | errors.push("create_avd_device not found") 141 | end 142 | if avd_scheme.create_avd_tag.nil? 143 | errors.push("create_avd_tag not found") 144 | end 145 | if avd_scheme.create_avd_abi.nil? 146 | errors.push("create_avd_abi not found") 147 | end 148 | if avd_scheme.create_avd_hardware_config_filepath.nil? 149 | errors.push("create_avd_hardware_config_filepath not found") 150 | end 151 | if avd_scheme.launch_avd_snapshot_filepath.nil? 152 | errors.push("launch_avd_snapshot_filepath not found") 153 | end 154 | if avd_scheme.launch_avd_launch_binary_name.nil? 155 | errors.push("launch_avd_launch_binary_name not found") 156 | end 157 | if avd_scheme.launch_avd_port.nil? 158 | errors.push("launch_avd_port not found") 159 | end 160 | if avd_scheme.launch_avd_additional_options.nil? 161 | errors.push("launch_avd_additional_options not found") 162 | end 163 | return errors 164 | end 165 | 166 | def self.throw_error(message) 167 | UI.message("Error: ".red + message.red) 168 | raise Exception, "Lane was stopped by plugin" 169 | end 170 | end 171 | end 172 | end -------------------------------------------------------------------------------- /lib/fastlane/plugin/automated_test_emulator_run/version.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module AutomatedTestEmulatorRun 3 | VERSION = "1.6.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/automated_test_emulator_run_action_spec.rb: -------------------------------------------------------------------------------- 1 | describe Fastlane::Actions::AutomatedTestEmulatorRunAction do 2 | describe '#run' do 3 | it 'prints a message' do 4 | expect(Fastlane::UI).to receive(:message).with("The automated_test_emulator_run plugin is working!") 5 | 6 | Fastlane::Actions::AutomatedTestEmulatorRunAction.run(nil) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | 3 | # This module is only used to check the environment is currently a testing env 4 | module SpecHelper 5 | end 6 | 7 | require 'fastlane' # to import the Action super class 8 | require 'fastlane/plugin/automated_test_emulator_run' # import the actual plugin 9 | 10 | Fastlane.load_actions # load other actions (in case your plugin calls other actions or shared values) 11 | --------------------------------------------------------------------------------