mirror of
https://github.com/budtmo/docker-android.git
synced 2026-07-30 19:23:22 +00:00
Refactored code, added log and emulator skins
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
WORKDIR = os.path.dirname(__file__)
|
||||
CONFIG_FILE = os.path.join(WORKDIR, 'nodeconfig.json')
|
||||
LOGGING_FILE = os.path.join(WORKDIR, 'logging.conf')
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
logging.basicConfig()
|
||||
logger = logging.getLogger('android')
|
||||
|
||||
|
||||
def get_api_level(android_version):
|
||||
"""
|
||||
Get api level of android version.
|
||||
|
||||
:param android_version: android version
|
||||
:type android_version: str
|
||||
:return: api version
|
||||
:rtype: str
|
||||
"""
|
||||
api_version = None
|
||||
|
||||
try:
|
||||
packages = get_available_sdk_packages()
|
||||
|
||||
if packages:
|
||||
item_pos = get_item_position(android_version, packages)
|
||||
logger.info('package in position: {pos}'.format(pos=item_pos))
|
||||
item = packages[item_pos]
|
||||
|
||||
item_info = item.split('-')
|
||||
api_version = re.search('%s(.*)%s' % ('API', ','), item_info[1]).group(1).strip()
|
||||
logger.info(
|
||||
'API level: {api}'.format(api=api_version))
|
||||
else:
|
||||
raise RuntimeError('List of packages is empty!')
|
||||
|
||||
except IndexError as i_err:
|
||||
logger.error(i_err)
|
||||
|
||||
return api_version
|
||||
|
||||
|
||||
def install_package(android_path, emulator_file, api_level, sys_img):
|
||||
"""
|
||||
Install sdk package.
|
||||
|
||||
:param android_path: location where android SDK is installed
|
||||
:type android_path: str
|
||||
:param emulator_file: emulator file that need to be link
|
||||
:type emulator_file: str
|
||||
:param api_level: api level
|
||||
:type api_level: str
|
||||
:param sys_img: system image of emulator
|
||||
:type sys_img: str
|
||||
"""
|
||||
# Link emulator shortcut
|
||||
emu_file = os.path.join(android_path, 'tools', emulator_file)
|
||||
emu_target = os.path.join(android_path, 'tools', 'emulator')
|
||||
os.symlink(emu_file, emu_target)
|
||||
|
||||
# Install package based on given android version
|
||||
cmd = 'echo y | android update sdk --no-ui -a -t android-{api},sys-img-{sys_img}-android-{api}'.format(
|
||||
api=api_level, sys_img=sys_img)
|
||||
logger.info('Android installation command : {install}'.format(install=cmd))
|
||||
subprocess.check_call('xterm -e \"{cmd}\"'.format(cmd=cmd), shell=True)
|
||||
|
||||
|
||||
def create_avd(android_path, avd_name, api_level):
|
||||
"""
|
||||
Create android virtual device.
|
||||
|
||||
:param android_path: location where android SDK is installed
|
||||
:type android_path: str
|
||||
:param avd_name: desire name
|
||||
:type avd_name: str
|
||||
:param api_level: api level
|
||||
:type api_level: str
|
||||
"""
|
||||
# Link emulator skins
|
||||
skins_rsc = os.path.join(android_path, 'skins')
|
||||
skins_dst = os.path.join(android_path, 'platforms', 'android-{api}'.format(api=api_level), 'skins')
|
||||
for skin_file in os.listdir(skins_rsc):
|
||||
os.symlink(os.path.join(skins_rsc, skin_file), os.path.join(skins_dst, skin_file))
|
||||
|
||||
# Create android emulator
|
||||
cmd = 'echo no | android create avd -f -n {name} -t android-{api}'.format(name=avd_name, api=api_level)
|
||||
logger.info('Emulator creation command : {cmd}'.format(cmd=cmd))
|
||||
subprocess.check_call('xterm -e \"{cmd}\"'.format(cmd=cmd), shell=True)
|
||||
|
||||
|
||||
def get_available_sdk_packages():
|
||||
"""
|
||||
Get list of available sdk packages.
|
||||
|
||||
:return: List of available packages.
|
||||
:rtype: bytearray
|
||||
"""
|
||||
cmd = ['android', 'list', 'sdk']
|
||||
output_str = subprocess.check_output(cmd)
|
||||
logger.info('List of Android SDK: ')
|
||||
logger.info(output_str)
|
||||
return [output.strip() for output in output_str.split('\n')] if output_str else None
|
||||
|
||||
|
||||
def get_item_position(keyword, items):
|
||||
"""
|
||||
Get position of item in array by given keyword.
|
||||
|
||||
:return: item position
|
||||
:rtype: int
|
||||
"""
|
||||
pos = 0
|
||||
for p, v in enumerate(items):
|
||||
if keyword in v:
|
||||
pos = p
|
||||
break # Get the first item that match with keyword
|
||||
return pos
|
||||
@@ -0,0 +1,84 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger('appium')
|
||||
|
||||
|
||||
def run(connect_to_grid, emulator_name, android_version):
|
||||
"""
|
||||
Run appium server.
|
||||
|
||||
:param connect_to_grid: option to connect with selenium grid
|
||||
:type connect_to_grid: bool
|
||||
:param emulator_name: name of emulator
|
||||
:type emulator_name: str
|
||||
:param android_version: android version
|
||||
:type android_version: str
|
||||
"""
|
||||
cmd = 'appium'
|
||||
if connect_to_grid:
|
||||
from src import CONFIG_FILE
|
||||
try:
|
||||
appium_host = os.getenv('APPIUM_HOST', '127.0.0.1')
|
||||
appium_port = int(os.getenv('APPIUM_PORT', 4723))
|
||||
selenium_host = os.getenv('SELENIUM_HOST', '172.17.0.1')
|
||||
selenium_port = int(os.getenv('SELENIUM_PORT', 4444))
|
||||
create_node_config(CONFIG_FILE, emulator_name, android_version,
|
||||
appium_host, appium_port, selenium_host, selenium_port)
|
||||
cmd += ' --nodeconfig {file}'.format(file=CONFIG_FILE)
|
||||
except ValueError as v_err:
|
||||
logger.error(v_err)
|
||||
subprocess.check_call('xterm -T "{name}" -n "{name}" -e \"{cmd}\"'.format(
|
||||
name=emulator_name, cmd=cmd), shell=True)
|
||||
|
||||
|
||||
def create_node_config(config_file, emulator_name, android_version, appium_host, appium_port,
|
||||
selenium_host, selenium_port):
|
||||
"""
|
||||
Create custom node config file in json format to be able to connect with selenium server.
|
||||
|
||||
:param config_file: config file
|
||||
:type config_file: str
|
||||
:param emulator_name: emulator name
|
||||
:type emulator_name: str
|
||||
:param android_version: android version of android emulator
|
||||
:type android_version: str
|
||||
:param appium_host: host where appium server is running
|
||||
:type appium_host: str
|
||||
:param appium_port: port number where where appium server is running
|
||||
:type appium_port: int
|
||||
:param selenium_host: host where selenium server is running
|
||||
:type selenium_host: str
|
||||
:param selenium_port: port number where selenium server is running
|
||||
:type selenium_port: int
|
||||
|
||||
"""
|
||||
config = {
|
||||
'capabilities': [
|
||||
{
|
||||
'platform': 'Android',
|
||||
'platformName': 'Android',
|
||||
'version': android_version,
|
||||
'browserName': emulator_name,
|
||||
'maxInstances': 1,
|
||||
}
|
||||
],
|
||||
'configuration': {
|
||||
'cleanUpCycle': 2000,
|
||||
'timeout': 30000,
|
||||
'proxy': 'org.openqa.grid.selenium.proxy.DefaultRemoteProxy',
|
||||
'url': 'http://{host}:{port}/wd/hub'.format(host=appium_host, port=appium_port),
|
||||
'host': appium_host,
|
||||
'port': appium_port,
|
||||
'maxSession': 6,
|
||||
'register': True,
|
||||
'registerCycle': 5000,
|
||||
'hubHost': selenium_host,
|
||||
'hubPort': selenium_port
|
||||
}
|
||||
}
|
||||
logger.info('appium node config: {config}'.format(config=config))
|
||||
with open(config_file, 'w') as cf:
|
||||
cf.write(json.dumps(config))
|
||||
@@ -0,0 +1,8 @@
|
||||
import logging
|
||||
import logging.config
|
||||
|
||||
from src import LOGGING_FILE
|
||||
|
||||
|
||||
def init():
|
||||
logging.config.fileConfig(LOGGING_FILE)
|
||||
@@ -0,0 +1,38 @@
|
||||
[loggers]
|
||||
keys=root, android, appium, service
|
||||
|
||||
[handlers]
|
||||
keys=console
|
||||
|
||||
[formatters]
|
||||
keys=formatter
|
||||
|
||||
[logger_root]
|
||||
level=INFO
|
||||
handlers=console
|
||||
|
||||
[logger_android]
|
||||
level=INFO
|
||||
handlers=console
|
||||
propagate=0
|
||||
qualname=android
|
||||
|
||||
[logger_appium]
|
||||
level=INFO
|
||||
handlers=console
|
||||
propagate=0
|
||||
qualname=appium
|
||||
|
||||
[logger_service]
|
||||
level=INFO
|
||||
handlers=console
|
||||
propagate=0
|
||||
qualname=service
|
||||
|
||||
[handler_console]
|
||||
class=StreamHandler
|
||||
formatter=formatter
|
||||
args=(sys.stdout,)
|
||||
|
||||
[formatter_formatter]
|
||||
format=[%(process)2d] [%(levelname)5s] %(name)s - %(message)s
|
||||
@@ -0,0 +1,59 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from src import android, appium, log
|
||||
|
||||
logger = logging.getLogger('service')
|
||||
|
||||
# not using enum because need to install pip that will make docker image size bigger
|
||||
TYPE_ARMEABI = 'armeabi'
|
||||
TYPE_X86 = 'x86'
|
||||
|
||||
|
||||
def start():
|
||||
"""
|
||||
Installation of needed sdk package, creation of android emulator and execution of appium server.
|
||||
|
||||
"""
|
||||
# Get all needed environment variables
|
||||
android_path = os.getenv('ANDROID_HOME', '/root')
|
||||
logger.info('Android path: {path}'.format(path=android_path))
|
||||
emulator_type = os.getenv('EMULATOR_TYPE', TYPE_ARMEABI).lower()
|
||||
logger.info('Emulator type: {type}'.format(type=emulator_type))
|
||||
android_version = os.getenv('ANDROID_VERSION', '4.2.2')
|
||||
logger.info('Android version: {version}'.format(version=android_version))
|
||||
connect_to_grid = str_to_bool(str(os.getenv('CONNECT_TO_GRID', False)))
|
||||
logger.info('Connect to selenium grid? {input}'.format(input=connect_to_grid))
|
||||
|
||||
# Install needed sdk packages
|
||||
emulator_type = TYPE_ARMEABI if emulator_type not in [TYPE_ARMEABI, TYPE_X86] else emulator_type
|
||||
emulator_file = 'emulator64-x86' if emulator_type == TYPE_X86 else 'emulator64-arm'
|
||||
logger.info('Emulator file: {file}'.format(file=emulator_file))
|
||||
api_level = android.get_api_level(android_version)
|
||||
sys_img = 'x86' if emulator_type == TYPE_X86 else 'armeabi-v7a'
|
||||
logger.info('System image: {sys_img}'.format(sys_img=sys_img))
|
||||
android.install_package(android_path, emulator_file, api_level, sys_img)
|
||||
|
||||
# Create android virtual device
|
||||
avd_name = 'emulator_{version}'.format(version=android_version)
|
||||
logger.info('AVD name: {avd}'.format(avd=avd_name))
|
||||
android.create_avd(android_path, avd_name, api_level)
|
||||
|
||||
# Run appium server
|
||||
appium.run(connect_to_grid, avd_name, android_version)
|
||||
|
||||
|
||||
def str_to_bool(str):
|
||||
"""
|
||||
Convert string to boolean.
|
||||
|
||||
:param str: given string
|
||||
:type str: str
|
||||
:return: converted string
|
||||
:rtype: bool
|
||||
"""
|
||||
return str.lower() in ('yes', 'true', 't', '1')
|
||||
|
||||
if __name__ == '__main__':
|
||||
log.init()
|
||||
start()
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Unit test for android.py."""
|
||||
from unittest import TestCase
|
||||
|
||||
import mock
|
||||
|
||||
from src import android
|
||||
|
||||
|
||||
@mock.patch('src.android.get_available_sdk_packages')
|
||||
class TestApiLevel(TestCase):
|
||||
"""Unit test class to test method get_api_level."""
|
||||
|
||||
def setUp(self):
|
||||
self.android_version = '4.2.2'
|
||||
|
||||
def test_get_api_level(self, mocked_packages):
|
||||
mocked_packages.return_value = ['9- SDK Platform Android 4.4.2, API 19, revision 4',
|
||||
'10- SDK Platform Android 4.3.1, API 18, revision 3',
|
||||
'11- SDK Platform Android 4.2.2, API 17, revision 3']
|
||||
api_level = android.get_api_level(self.android_version)
|
||||
self.assertEqual(api_level, '17')
|
||||
|
||||
def test_empty_packages(self, mocked_packages):
|
||||
mocked_packages.return_value = None
|
||||
with self.assertRaises(RuntimeError):
|
||||
android.get_api_level(self.android_version)
|
||||
|
||||
def test_index_error(self, mocked_packages):
|
||||
mocked_packages.return_value = ['9 SDK Platform Android 4.4.2, API 19, revision 4',
|
||||
'10 SDK Platform Android 4.3.1, API 18, revision 3',
|
||||
'11 SDK Platform Android 4.2.2, API 17, revision 3']
|
||||
android.get_api_level(self.android_version)
|
||||
self.assertRaises(IndexError)
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Unit test for android.py."""
|
||||
from unittest import TestCase
|
||||
|
||||
import mock
|
||||
|
||||
from src import android
|
||||
|
||||
|
||||
class TestAvailablePackages(TestCase):
|
||||
"""Unit test class to test method get_available_sdk_packages."""
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_valid_output(self, mocked_output):
|
||||
mocked_output.return_value = 'package 1 \n package 2'
|
||||
output = android.get_available_sdk_packages()
|
||||
self.assertEqual(['package 1', 'package 2'], output)
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_without_line_break(self, mocked_output):
|
||||
mocked_output.return_value = 'package 1, package 2'
|
||||
output = android.get_available_sdk_packages()
|
||||
self.assertEqual(['package 1, package 2'], output)
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_empty_string(self, mocked_output):
|
||||
mocked_output.return_value = None
|
||||
output = android.get_available_sdk_packages()
|
||||
self.assertEqual(None, output)
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Unit test for android.py."""
|
||||
from unittest import TestCase
|
||||
|
||||
import mock
|
||||
|
||||
from src import android
|
||||
|
||||
|
||||
class TestAvd(TestCase):
|
||||
"""Unit test class to test method create_avd."""
|
||||
|
||||
def setUp(self):
|
||||
self.android_path = '/root'
|
||||
self.avd_name = 'test'
|
||||
self.api_level = 21
|
||||
|
||||
@mock.patch('os.symlink')
|
||||
@mock.patch('subprocess.check_call')
|
||||
def test_avd_creation(self, mocked_sys_link, mocked_suprocess):
|
||||
with mock.patch('os.listdir') as mocked_list_dir:
|
||||
mocked_list_dir.return_value = ['file1', 'file2']
|
||||
self.assertFalse(mocked_list_dir.called)
|
||||
self.assertFalse(mocked_sys_link.called)
|
||||
self.assertFalse(mocked_suprocess.called)
|
||||
android.create_avd(self.android_path, self.avd_name, self.api_level)
|
||||
self.assertTrue(mocked_list_dir.called)
|
||||
self.assertTrue(mocked_sys_link.called)
|
||||
self.assertTrue(mocked_suprocess.called)
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Unit test for android.py."""
|
||||
from unittest import TestCase
|
||||
|
||||
import mock
|
||||
|
||||
from src import android
|
||||
|
||||
|
||||
class TestInstallPackage(TestCase):
|
||||
"""Unit test class to test method install_package."""
|
||||
|
||||
def setUp(self):
|
||||
self.android_path = '/root'
|
||||
self.emulator_file = 'emulator64-arm'
|
||||
self.api_level = 21
|
||||
self.sys_img = 'armeabi-v7a'
|
||||
|
||||
@mock.patch('os.symlink')
|
||||
@mock.patch('subprocess.check_call')
|
||||
def test_package_installation(self, mocked_sys_link, mocked_suprocess):
|
||||
self.assertFalse(mocked_sys_link.called)
|
||||
self.assertFalse(mocked_suprocess.called)
|
||||
android.install_package(self.android_path, self.emulator_file, self.api_level, self.sys_img)
|
||||
self.assertTrue(mocked_sys_link.called)
|
||||
self.assertTrue(mocked_suprocess.called)
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Unit test for android.py."""
|
||||
from unittest import TestCase
|
||||
|
||||
from src import android
|
||||
|
||||
|
||||
class TestItemPosition(TestCase):
|
||||
"""Unit test class to test method get_item_position."""
|
||||
|
||||
def setUp(self):
|
||||
self.items = ['android 4.1', 'android 4.2.2', 'android 4.3', 'android 4.4', 'android 4.4.2']
|
||||
|
||||
def test_valid_params(self):
|
||||
keyword = '4.2'
|
||||
output = android.get_item_position(keyword, self.items)
|
||||
self.assertEqual(1, output)
|
||||
|
||||
def test_invalid_keyword(self):
|
||||
keyword = 'fake'
|
||||
output = android.get_item_position(keyword, self.items)
|
||||
self.assertEqual(0, output)
|
||||
|
||||
def test_empty_array(self):
|
||||
items = []
|
||||
keyword = '4.2'
|
||||
output = android.get_item_position(keyword, items)
|
||||
self.assertEqual(0, output)
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Unit test for appium.py."""
|
||||
import os
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from src import CONFIG_FILE, appium
|
||||
|
||||
|
||||
class TestAppiumConfig(TestCase):
|
||||
"""Unit test class to test method create_node_config."""
|
||||
|
||||
def test_config_creation(self):
|
||||
self.assertFalse(os.path.exists(CONFIG_FILE))
|
||||
appium.create_node_config(CONFIG_FILE, 'emulator_name', '4.2.2', '127.0.0.1', 4723, '127.0.0.1', 4444)
|
||||
self.assertTrue(os.path.exists(CONFIG_FILE))
|
||||
os.remove(CONFIG_FILE)
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Unit test for appium.py."""
|
||||
from unittest import TestCase
|
||||
|
||||
import os
|
||||
|
||||
import mock
|
||||
|
||||
from src import appium
|
||||
|
||||
|
||||
@mock.patch('subprocess.check_call')
|
||||
class TestAppiumConfig(TestCase):
|
||||
"""Unit test class to test method run."""
|
||||
|
||||
def setUp(self):
|
||||
self.emulator_name = 'test'
|
||||
self.android_version = '4.2.2'
|
||||
|
||||
def test_without_selenium_grid(self, mocked_subprocess):
|
||||
with mock.patch('src.appium.create_node_config') as mocked_config:
|
||||
self.assertFalse(mocked_config.called)
|
||||
self.assertFalse(mocked_subprocess.called)
|
||||
appium.run(False, self.emulator_name, self.android_version)
|
||||
self.assertFalse(mocked_config.called)
|
||||
self.assertTrue(mocked_subprocess.called)
|
||||
|
||||
def test_with_selenium_grid(self, mocked_subprocess):
|
||||
with mock.patch('src.appium.create_node_config') as mocked_config:
|
||||
self.assertFalse(mocked_config.called)
|
||||
self.assertFalse(mocked_subprocess.called)
|
||||
appium.run(True, self.emulator_name, self.android_version)
|
||||
self.assertTrue(mocked_config.called)
|
||||
self.assertTrue(mocked_subprocess.called)
|
||||
|
||||
def test_invalid_integer(self, mocked_subprocess):
|
||||
os.environ['APPIUM_PORT'] = 'test'
|
||||
with mock.patch('src.appium.create_node_config') as mocked_config:
|
||||
self.assertFalse(mocked_config.called)
|
||||
self.assertFalse(mocked_subprocess.called)
|
||||
appium.run(True, self.emulator_name, self.android_version)
|
||||
self.assertFalse(mocked_config.called)
|
||||
self.assertTrue(mocked_subprocess.called)
|
||||
self.assertRaises(ValueError)
|
||||
|
||||
def tearDown(self):
|
||||
if os.getenv('APPIUM_PORT'):
|
||||
del os.environ['APPIUM_PORT']
|
||||
Reference in New Issue
Block a user