From 645e948676f2f2b02ef9b09daff1c282fdf4cd0f Mon Sep 17 00:00:00 2001 From: Venelin Valkov Date: Sat, 25 Mar 2017 00:28:21 +0200 Subject: [PATCH] first commit --- .gitignore | 94 ++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 +++++++++++ README.md | 1 + appgym/__init__.py | 83 ++++++++++++++++++++++++++++++++++++++++ setup.py | 11 ++++++ 5 files changed, 210 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 appgym/__init__.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c1e73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fec32e8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Venelin Valkov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d558cd --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# appgym diff --git a/appgym/__init__.py b/appgym/__init__.py new file mode 100644 index 0000000..d4dc512 --- /dev/null +++ b/appgym/__init__.py @@ -0,0 +1,83 @@ +from uiautomator import Device +import numpy as np +import timeit +import urllib3 + +from scipy import misc + +import pandas as pd + +import sys +import os + +from subprocess import call + +FNULL = open(os.devnull, 'w') +PROJECT_ROOT = "/Users/vini/Dev/uni/dissertation/code/" +APP_UNDER_TEST_ROOT = "/Users/vini/Dev/uni/dissertation/code/sample_app/" + +http_client = urllib3.PoolManager() + +class Action: + + def __init__(self, gui_object, action_type='click'): + self.gui_object = gui_object + self.action_type = action_type + + def execute(self): + if self.action_type == 'click': + self.gui_object.click() + +class AndroidEnv: + + def __init__(self, app_package, screen_size, resize_scale=0.1, coverage_target=0.8): + self.app_package = app_package + self.device = Device() + self.screen_size = screen_size + self._exec(f"ng ng-cp {PROJECT_ROOT}lib/org.jacoco.ant-0.7.9-nodeps.jar") + self._exec(f"ng ng-cp {PROJECT_ROOT}") + self._exec("adb forward tcp:8981 tcp:8981") + self.resize_scale = resize_scale + self.coverage_target = coverage_target + + def reset(self): + self._exec(f"adb shell am force-stop {self.app_package}") + self._exec(f"adb shell pm clear {self.app_package}") + self._exec(f"adb shell monkey -p {self.app_package} 1") + return self._get_screen(), self._get_actions() + + def step(self, action): + action.execute() + coverage = self._get_current_coverage() + done = coverage > self.coverage_target + return self._get_screen(), self._get_actions(), coverage, done + + def _exec(self, command): + call(command, shell=True, stdout=FNULL) + + def _get_actions(self): + actions = [] + for gui_obj in self.device(): + if gui_obj.clickable: + actions.append(Action(gui_obj)) + return actions + + def _get_screen(self): + self.device.screenshot("state.png") + img = misc.imread("state.png") + return self._image_to_torch(img) + + def _image_to_torch(self, image): + img_resized = misc.imresize(image, size=self.resize_scale) + return np.ascontiguousarray(img_resized, dtype=np.float32) / 255 + + def _get_current_coverage(self): + start_time = timeit.default_timer() + with http_client.request("GET", "http://localhost:8981", preload_content=False) as r, open("coverage/coverage.exec", "wb") as coverage_file: + coverage_file.write(r.read()) + generate_report_cmd = f"ng ReportGenerator {APP_UNDER_TEST_ROOT}" + self._exec(generate_report_cmd) + df = pd.read_csv("coverage/report.csv") + missed, covered = df[['LINE_MISSED', 'LINE_COVERED']].sum() + print(f"Complete in {timeit.default_timer() - start_time} seconds") + return covered / (missed + covered) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..72757cb --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup + +setup(name='appgym', + version='0.1', + description='Android Environment for Reinforcement Learning', + url='https://github.com/appgym/appgym', + author='Venelin Valkov', + author_email='venelin@curiousily.com', + license='MIT', + packages=['appgym'], + zip_safe=False)