From 31dfac1b01c72491fc2294f56dbd72d61505cf6b Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Sun, 2 Dec 2018 00:40:15 +0200 Subject: [PATCH 01/15] feat: new run command --- core/utils/process/__init__.py | 0 core/utils/process/run.py | 81 ++++++++++++++++++++++++++++++++ core_tests/core/process_tests.py | 9 ++-- core_tests/core/run_tests.py | 58 +++++++++++++++++++++++ 4 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 core/utils/process/__init__.py create mode 100644 core/utils/process/run.py create mode 100644 core_tests/core/run_tests.py diff --git a/core/utils/process/__init__.py b/core/utils/process/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/core/utils/process/run.py b/core/utils/process/run.py new file mode 100644 index 00000000..e330ee98 --- /dev/null +++ b/core/utils/process/run.py @@ -0,0 +1,81 @@ +import logging +import os +import subprocess +import time +from datetime import datetime + +import psutil +from psutil import Popen, TimeoutExpired + +from core.base_test.test_context import TestContext +from core.log.log import Log +from core.settings import Settings +from core.utils.file_utils import File +from core.utils.process_info import ProcessInfo + + +def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False, register=True, + log_level=logging.DEBUG): + # Init result values + log_file = None + complete = False + duration = None + output = '' + + # Command settings + if not wait: + time_string = datetime.now().strftime('%Y_%m_%d_%H_%M_%S') + log_file = os.path.join(Settings.TEST_OUT_LOGS, 'command_{0}.txt'.format(time_string)) + File.write(path=log_file, text=cmd + os.linesep + '====>' + os.linesep) + cmd = cmd + ' >> ' + log_file + ' 2>&1 &' + + # Log command that will be executed: + Log.log(level=log_level, message='Execute command: ' + cmd) + Log.log(level=logging.DEBUG, message='CWD: ' + cwd) + + # Execute command: + if wait: + start = time.time() + process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + # Wait until command complete + try: + process.wait() + complete = True + out, err = process.communicate() + if out is not None: + output = str(out.decode('utf-8')).strip() + if err is not None: + output = os.linesep + str(err.decode('utf-8')).strip() + + except TimeoutExpired: + process.kill() + if fail_safe: + Log.error('Command "{0}" timeout after {1} seconds.'.format(cmd, timeout)) + else: + raise + end = time.time() + duration = end - start + else: + process = Popen(cmd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, cwd=cwd) + + # Get result + pid = process.pid + exit_code = process.returncode + + # Log output of the process + if wait: + Log.log(level=log_level, message='OUTPUT: ' + os.linesep + output) + else: + Log.log(level=log_level, message='OUTPUT REDIRECTED: ' + log_file) + + # Construct result + result = ProcessInfo(cmd=cmd, pid=pid, exit_code=exit_code, output=output, log_file=log_file, complete=complete, + duration=duration) + + # Register in TestContext + if psutil.pid_exists(result.pid) and register: + TestContext.STARTED_PROCESSES.append(result) + + # Return the result + return result diff --git a/core_tests/core/process_tests.py b/core_tests/core/process_tests.py index e0e239e6..07e15c74 100644 --- a/core_tests/core/process_tests.py +++ b/core_tests/core/process_tests.py @@ -21,8 +21,9 @@ class ProcessTests(unittest.TestCase): def tearDown(self): for process in TestContext.STARTED_PROCESSES: - Log.info("Kill Process: " + os.linesep + process.commandline) - Process.kill_pid(process.pid) + if Process.is_running(process.pid): + Log.info("Kill Process: " + os.linesep + process.commandline) + Process.kill_pid(process.pid) def test_01_run_simple_command(self): home = expanduser("~") @@ -39,10 +40,10 @@ def test_02_run_command_without_wait_for_completion(self): result = Run.command(cmd='pause', wait=False) time.sleep(1) assert result.exit_code is None, 'exit code should be None when command is not complete.' - assert result.complete is False, 'tail command should not exit.' + assert result.complete is False, 'pause command should not exit.' assert result.duration is None, 'duration should be None in case process is not complete' assert result.output is '', 'output should be empty string.' - assert result.log_file is not None, 'stdout and stderr of tail command should be redirected to file.' + assert result.log_file is not None, 'stdout and stderr of pause command should be redirected to file.' assert 'pause' in File.read(result.log_file), 'Log file should contains cmd of the command.' assert 'Press any key' in File.read(result.log_file), 'Log file should contains output of the command.' else: diff --git a/core_tests/core/run_tests.py b/core_tests/core/run_tests.py new file mode 100644 index 00000000..65756438 --- /dev/null +++ b/core_tests/core/run_tests.py @@ -0,0 +1,58 @@ +import os +import platform +import unittest +from os.path import expanduser + +from psutil import TimeoutExpired + +from core.settings import Settings +from core.utils.file_utils import File +from core.utils.process.run import run + + +# noinspection PyMethodMayBeStatic +@unittest.skipIf('Windows' in platform.platform(), 'This test class can not be exececuted on Windows OS.') +class RunPosixTests(unittest.TestCase): + + def test_01_run_simple_command(self): + home = expanduser("~") + result = run(cmd='ls ' + home, wait=True, timeout=1) + assert result.exit_code == 0, 'Wrong exit code of successful command.' + assert result.log_file is None, 'No log file should be generated if wait=True.' + assert result.complete is True, 'Complete should be true when process execution is complete.' + assert result.duration < 1, 'Process duration took too much time.' + assert 'Desktop' in result.output, 'Listing home do not include Desktop folder.' + + def test_02_run_command_with_redirect(self): + home = expanduser("~") + out_file = os.path.join(Settings.TEST_OUT_HOME, 'log.txt') + result = run(cmd='ls ' + home + ' > ' + out_file, wait=True, timeout=1) + assert result.exit_code == 0, 'Wrong exit code of successful command.' + assert result.log_file is None, 'No log file should be generated if wait=True.' + assert result.complete is True, 'Complete should be true when process execution is complete.' + assert result.duration < 1, 'Process duration took too much time.' + assert result.output == '', 'Output should be empty.' + assert 'Desktop' in File.read(path=out_file) + + def test_03_run_command_with_pipe(self): + result = run(cmd='echo "test case" | wc -w ', wait=True, timeout=1) + assert result.exit_code == 0, 'Wrong exit code of successful command.' + assert result.log_file is None, 'No log file should be generated if wait=True.' + assert result.complete is True, 'Complete should be true when process execution is complete.' + assert result.duration < 1, 'Process duration took too much time.' + assert result.output == '2', 'Output should be 2.' + + def test_10_run_command_with_wait_true_that_exceed_timeout(self): + try: + run(cmd='sleep 3', wait=True, timeout=1, fail_safe=False) + assert False, 'This line should not be executed, because the line above should raise an exception.' + except TimeoutExpired: + pass + + def test_11_run_command_with_wait_true_and_fail_safe_that_exceed_timeout(self): + result = run(cmd='sleep 3', wait=True, timeout=1, fail_safe=True) + assert result.exit_code is None, 'Exit code on non completed programs should be None.' + assert result.log_file is None, 'No log file should be generated if wait=True.' + assert result.complete is False, 'Complete should be true when process execution is complete.' + assert result.duration < 2, 'Process duration should be same as timeout.' + assert result.output == '', 'No output for not compelted programs.' From 700e7485934873beaaaf3fbafd000ea37917a8ab Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Sun, 2 Dec 2018 23:07:26 +0200 Subject: [PATCH 02/15] fix: running command with timeout --- README.md | 35 ++++++++++++++++++++++++++++------- core/utils/process/run.py | 17 +++++++++++------ core_tests/core/run_tests.py | 7 +++---- requirements_darwin.txt | 1 + 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 28152c0c..8571784c 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,41 @@ Project with test for NativeScript tooling. -## Install Requirements +## Requirements + +**Posix:** +- Python 2.7 or Python 3.2+ + +**Windows** +- Python 3.2+ -Install Python 2.*: -``` -brew install python -``` + +## Before Running Tests + +**Install Required Packages** Update `pip` and install project requirements: ``` python -m pip install --upgrade pip -pip install -r requirements.txt --user ``` -## Before Running Tests +Install packages on macOS: +```bash +pip install --upgrade -r requirements_darwin.txt --user +``` +Install packages on Windows on Linux: +```bash +pip install --upgrade -r requirements.txt --user +``` + +Set `PYTHONUNBUFFERED` and `PYTHONIOENCODING` environment variables: +```bash +export PYTHONUNBUFFERED=1 +export PYTHONIOENCODING=utf-8 +``` +Notes: +- `PYTHONUNBUFFERED` is required to get logging on Jenkins CI working properly. +- `PYTHONIOENCODING` helps to get command execution more stable. **Setup Machine** diff --git a/core/utils/process/run.py b/core/utils/process/run.py index e330ee98..1328c810 100644 --- a/core/utils/process/run.py +++ b/core/utils/process/run.py @@ -1,11 +1,10 @@ import logging import os -import subprocess +import sys import time from datetime import datetime import psutil -from psutil import Popen, TimeoutExpired from core.base_test.test_context import TestContext from core.log.log import Log @@ -13,6 +12,13 @@ from core.utils.file_utils import File from core.utils.process_info import ProcessInfo +if os.name == 'posix' and sys.version_info[0] < 3: + # Import subprocess32 on Posix when Python2 is detected + # noinspection PyPackageRequirements + import subprocess32 as subprocess +else: + import subprocess + def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False, register=True, log_level=logging.DEBUG): @@ -40,15 +46,14 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False # Wait until command complete try: - process.wait() + process.wait(timeout=timeout) complete = True out, err = process.communicate() if out is not None: output = str(out.decode('utf-8')).strip() if err is not None: output = os.linesep + str(err.decode('utf-8')).strip() - - except TimeoutExpired: + except subprocess.TimeoutExpired: process.kill() if fail_safe: Log.error('Command "{0}" timeout after {1} seconds.'.format(cmd, timeout)) @@ -57,7 +62,7 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False end = time.time() duration = end - start else: - process = Popen(cmd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, cwd=cwd) + process = psutil.Popen(cmd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, cwd=cwd) # Get result pid = process.pid diff --git a/core_tests/core/run_tests.py b/core_tests/core/run_tests.py index 65756438..874e2f5f 100644 --- a/core_tests/core/run_tests.py +++ b/core_tests/core/run_tests.py @@ -3,8 +3,6 @@ import unittest from os.path import expanduser -from psutil import TimeoutExpired - from core.settings import Settings from core.utils.file_utils import File from core.utils.process.run import run @@ -43,10 +41,11 @@ def test_03_run_command_with_pipe(self): assert result.output == '2', 'Output should be 2.' def test_10_run_command_with_wait_true_that_exceed_timeout(self): + # noinspection PyBroadException try: run(cmd='sleep 3', wait=True, timeout=1, fail_safe=False) assert False, 'This line should not be executed, because the line above should raise an exception.' - except TimeoutExpired: + except Exception: pass def test_11_run_command_with_wait_true_and_fail_safe_that_exceed_timeout(self): @@ -55,4 +54,4 @@ def test_11_run_command_with_wait_true_and_fail_safe_that_exceed_timeout(self): assert result.log_file is None, 'No log file should be generated if wait=True.' assert result.complete is False, 'Complete should be true when process execution is complete.' assert result.duration < 2, 'Process duration should be same as timeout.' - assert result.output == '', 'No output for not compelted programs.' + assert result.output == '', 'No output for not completed programs.' diff --git a/requirements_darwin.txt b/requirements_darwin.txt index e8c5dd8f..5f8fb141 100644 --- a/requirements_darwin.txt +++ b/requirements_darwin.txt @@ -11,3 +11,4 @@ pytesseract>=0.2.5 flake8>=3.6.0 atomac>=1.1.0 PyObjC>=5.1.1 +subprocess32>=3.5.3 From 4d76eb4cb47f7577a200f8e263d4e7902bee6abf Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Mon, 3 Dec 2018 00:18:16 +0200 Subject: [PATCH 03/15] fix: kill of processes in TestContext --- core/base_test/tns_test.py | 14 ++------------ core/utils/process.py | 6 ++++++ core/utils/process/__init__.py | 0 core/utils/{process => }/run.py | 6 ++++-- core_tests/core/run_tests.py | 24 +++++++++++++++++++++++- products/nativescript/tns.py | 5 +++-- 6 files changed, 38 insertions(+), 17 deletions(-) delete mode 100644 core/utils/process/__init__.py rename core/utils/{process => }/run.py (90%) diff --git a/core/base_test/tns_test.py b/core/base_test/tns_test.py index 7bb6b129..b05c72b2 100644 --- a/core/base_test/tns_test.py +++ b/core/base_test/tns_test.py @@ -27,7 +27,6 @@ def setUpClass(cls): Tns.kill() Gradle.kill() TnsTest.kill_emulators() - cls.kill_processes() # Ensure log folders are create Folder.create(Settings.TEST_OUT_HOME) @@ -50,7 +49,7 @@ def setUp(self): def tearDown(self): # Kill processes Tns.kill() - self.kill_processes() + Process.kill_all_in_context() # Analise test result result = self._resultForDoCleanups @@ -69,18 +68,9 @@ def tearDownClass(cls): """ Tns.kill() TnsTest.kill_emulators() - for process in TestContext.STARTED_PROCESSES: - Log.info("Kill Process: " + os.linesep + process.commandline) - Process.kill_pid(process.pid) + Process.kill_all_in_context() Log.test_class_end(class_name=cls.__name__) - @staticmethod - def kill_processes(): - for process in TestContext.STARTED_PROCESSES: - if Process.is_running(process.pid): - Log.info("Kill Process: " + os.linesep + process.commandline) - Process.kill_pid(process.pid) - @staticmethod def kill_emulators(): DeviceManager.Emulator.stop() diff --git a/core/utils/process.py b/core/utils/process.py index 9a350f20..f61f532a 100644 --- a/core/utils/process.py +++ b/core/utils/process.py @@ -212,3 +212,9 @@ def kill_by_handle(file_path): proc.kill() except Exception: continue + + @staticmethod + def kill_all_in_context(): + for process in TestContext.STARTED_PROCESSES: + name = process.commandline.split(' ')[0] + Process.kill(proc_name=name, proc_cmdline=Settings.TEST_RUN_HOME) diff --git a/core/utils/process/__init__.py b/core/utils/process/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/core/utils/process/run.py b/core/utils/run.py similarity index 90% rename from core/utils/process/run.py rename to core/utils/run.py index 1328c810..f29f23cb 100644 --- a/core/utils/process/run.py +++ b/core/utils/run.py @@ -1,5 +1,6 @@ import logging import os +import signal import sys import time from datetime import datetime @@ -42,7 +43,7 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False # Execute command: if wait: start = time.time() - process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + process = subprocess.Popen(cmd, cwd=cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Wait until command complete try: @@ -62,7 +63,8 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False end = time.time() duration = end - start else: - process = psutil.Popen(cmd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, cwd=cwd) + process = psutil.Popen(cmd, cwd=cwd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, + preexec_fn=os.setsid) # Get result pid = process.pid diff --git a/core_tests/core/run_tests.py b/core_tests/core/run_tests.py index 874e2f5f..cc838152 100644 --- a/core_tests/core/run_tests.py +++ b/core_tests/core/run_tests.py @@ -1,17 +1,24 @@ import os import platform +import time import unittest from os.path import expanduser +from nose.tools import timed + from core.settings import Settings from core.utils.file_utils import File -from core.utils.process.run import run +from core.utils.process import Process +from core.utils.run import run # noinspection PyMethodMayBeStatic @unittest.skipIf('Windows' in platform.platform(), 'This test class can not be exececuted on Windows OS.') class RunPosixTests(unittest.TestCase): + def tearDown(self): + Process.kill_all_in_context() + def test_01_run_simple_command(self): home = expanduser("~") result = run(cmd='ls ' + home, wait=True, timeout=1) @@ -55,3 +62,18 @@ def test_11_run_command_with_wait_true_and_fail_safe_that_exceed_timeout(self): assert result.complete is False, 'Complete should be true when process execution is complete.' assert result.duration < 2, 'Process duration should be same as timeout.' assert result.output == '', 'No output for not completed programs.' + + @timed(5) + def test_20_run_long_living_process(self): + file_path = os.path.join(Settings.TEST_OUT_HOME, 'temp.txt') + File.write(path=file_path, text='test') + result = run(cmd='tail -f ' + file_path, wait=False) + time.sleep(1) + Process.kill_pid(pid=result.pid) + assert result.exit_code is None, 'exit code should be None when command is not complete.' + assert result.complete is False, 'tail command should not exit.' + assert result.duration is None, 'duration should be None in case process is not complete' + assert result.output is '', 'output should be empty string.' + assert result.log_file is not None, 'stdout and stderr of tail command should be redirected to file.' + assert 'tail' in File.read(result.log_file), 'Log file should contains cmd of the command.' + assert 'test' in File.read(result.log_file), 'Log file should contains output of the command.' diff --git a/products/nativescript/tns.py b/products/nativescript/tns.py index f18e7cad..18f308db 100644 --- a/products/nativescript/tns.py +++ b/products/nativescript/tns.py @@ -7,7 +7,8 @@ from core.enums.platform_type import Platform from core.settings import Settings from core.utils.file_utils import Folder, File -from core.utils.process import Run, Process +from core.utils.process import Process +from core.utils.run import run from products.nativescript.app import App from products.nativescript.tns_assert import TnsAssert @@ -73,7 +74,7 @@ def exec_command(command, cwd=Settings.TEST_RUN_HOME, platform=Platform.NONE, em cmd += ' --justlaunch' if log_trace: cmd += ' --log trace' - return Run.command(cmd=cmd, cwd=cwd, wait=wait, log_level=logging.INFO, timeout=timeout) + return run(cmd=cmd, cwd=cwd, wait=wait, log_level=logging.INFO, timeout=timeout) @staticmethod def create(app_name=Settings.AppName.DEFAULT, template=None, path=None, app_id=None, From b900df7d9e07b64ee662fb704ecb87355927a83a Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Mon, 3 Dec 2018 16:17:05 +0200 Subject: [PATCH 04/15] feat: python 3.7 compatibility --- SETUP.md | 2 +- core/base_test/tns_test.py | 8 ++- core/log/log.py | 4 +- core/settings/Settings.py | 13 +++-- core/utils/device/adb.py | 7 +-- core/utils/device/device.py | 6 +-- core/utils/device/device_manager.py | 13 ++--- core/utils/device/simctl.py | 5 +- core/utils/git.py | 4 +- core/utils/gradle.py | 9 ++-- core/utils/npm.py | 4 +- core/utils/process.py | 81 +++-------------------------- core/utils/run.py | 24 ++++----- core/utils/screen.py | 36 ++++++------- core/utils/xcode.py | 6 +-- core_tests/core/adb_tests.py | 5 +- core_tests/core/device_tests.py | 1 - core_tests/core/run_tests.py | 20 ++++--- products/nativescript/app.py | 6 +-- 19 files changed, 102 insertions(+), 152 deletions(-) diff --git a/SETUP.md b/SETUP.md index a04a1ed2..9d65e887 100644 --- a/SETUP.md +++ b/SETUP.md @@ -2,7 +2,7 @@ ## Install Tesseract -In order to get OCR features workign you need to install `tesseract`. +In order to get OCR features working you need to install `tesseract`. **macOS** ```bash diff --git a/core/base_test/tns_test.py b/core/base_test/tns_test.py index b05c72b2..12595dc0 100644 --- a/core/base_test/tns_test.py +++ b/core/base_test/tns_test.py @@ -52,7 +52,13 @@ def tearDown(self): Process.kill_all_in_context() # Analise test result - result = self._resultForDoCleanups + if Settings.PYTHON_VERSION < 3: + # noinspection PyUnresolvedReferences + result = self._resultForDoCleanups + else: + # noinspection PyUnresolvedReferences + result = self._outcome.result + outcome = 'FAILED' if result.errors == [] and result.failures == []: outcome = 'PASSED' diff --git a/core/log/log.py b/core/log/log.py index 7d833cf6..4b7ca093 100644 --- a/core/log/log.py +++ b/core/log/log.py @@ -8,10 +8,10 @@ class Log(object): @staticmethod - def log(level, message): + def log(level, msg): if level != logging.DEBUG: date = datetime.datetime.now().strftime('%H:%M:%S') - print '{0} {1}'.format(date, message) + print('{0} {1}'.format(date, msg)) @staticmethod def debug(message): diff --git a/core/settings/Settings.py b/core/settings/Settings.py index 7b82cbd1..a8cae734 100644 --- a/core/settings/Settings.py +++ b/core/settings/Settings.py @@ -1,6 +1,7 @@ import logging import os import platform +import sys from core.enums.env import EnvironmentType from core.enums.os_type import OSType @@ -17,6 +18,10 @@ def get_os(): return OSType.LINUX +def get_python_version(): + return sys.version_info[0] + + def get_env(): env = os.environ.get('TEST_ENV', 'next') if 'next' in env: @@ -34,14 +39,14 @@ def get_project_home(): return home +HOST_OS = get_os() +PYTHON_VERSION = get_python_version() +ENV = get_env() + LOG_LEVEL = logging.DEBUG NS_GIT_ORG = 'NativeScript' -HOST_OS = get_os() - -ENV = get_env() - TEST_RUN_HOME = get_project_home() TEST_SUT_HOME = os.path.join(TEST_RUN_HOME, 'sut') diff --git a/core/utils/device/adb.py b/core/utils/device/adb.py index 7e31979f..e2ee9586 100644 --- a/core/utils/device/adb.py +++ b/core/utils/device/adb.py @@ -5,7 +5,8 @@ from core.enums.os_type import OSType from core.settings import Settings from core.utils.file_utils import File -from core.utils.process import Run, Process +from core.utils.process import Process +from utils.run import run ANDROID_HOME = os.environ.get('ANDROID_HOME') ADB_PATH = os.path.join(ANDROID_HOME, 'platform-tools', 'adb') @@ -19,7 +20,7 @@ def __run_adb_command(command, id=None, wait=True, timeout=60, fail_safe=False, command = '{0} {1}'.format(ADB_PATH, command) else: command = '{0} -s {1} {2}'.format(ADB_PATH, id, command) - return Run.command(cmd=command, wait=wait, timeout=timeout, fail_safe=fail_safe, log_level=log_level) + return run(cmd=command, wait=wait, timeout=timeout, fail_safe=fail_safe, log_level=log_level) @staticmethod def __get_ids(include_emulator=False): @@ -146,7 +147,7 @@ def is_text_visible(id, text, case_sensitive=False): def get_screen(id, file_path): File.clean(path=file_path) if Settings.OSType == OSType.WINDOWS: - Adb.__run_adb_command(command='exec-out screencap -p > ' + file_path, id=id, log_level=logging.INFO) + Adb.__run_adb_command(command='exec-out screencap -p > ' + file_path, id=id, log_level=logging.DEBUG) else: Adb.__run_adb_command(command='shell rm /sdcard/screen.png', id=id) Adb.__run_adb_command(command='shell screencap -p /sdcard/screen.png', id=id) diff --git a/core/utils/device/device.py b/core/utils/device/device.py index 2bd8b20d..989bf8af 100644 --- a/core/utils/device/device.py +++ b/core/utils/device/device.py @@ -11,8 +11,8 @@ from core.utils.device.simctl import Simctl from core.utils.file_utils import File, Folder from core.utils.image_utils import ImageUtils -from core.utils.process import Run from core.utils.wait import Wait +from utils.run import run if Settings.HOST_OS is OSType.OSX: from core.utils.device.simauto import SimAuto @@ -26,7 +26,7 @@ def __init__(self, id, name, type, version): self.version = version if type is DeviceType.IOS: - type = Run.command(cmd="ideviceinfo | grep ProductType") + type = run(cmd="ideviceinfo | grep ProductType") type = type.replace(',', '') type = type.replace('ProductType:', '').strip(' ') self.name = type @@ -114,7 +114,7 @@ def get_screen(self, path, log_level=logging.INFO): image_saved = True if image_saved: message = "Image of {0} saved at {1}".format(self.id, path) - Log.log(level=log_level, message=message) + Log.log(level=log_level, msg=message) else: message = "Failed to save image of {0} saved at {1}".format(self.id, path) Log.error(message) diff --git a/core/utils/device/device_manager.py b/core/utils/device/device_manager.py index 0db16ca1..f1c8eed4 100644 --- a/core/utils/device/device_manager.py +++ b/core/utils/device/device_manager.py @@ -7,7 +7,8 @@ from core.utils.device.device import Device from core.utils.device.idevice import IDevice from core.utils.device.simctl import Simctl -from core.utils.process import Run, Process +from core.utils.process import Process +from utils.run import run class DeviceManager(object): @@ -59,7 +60,7 @@ def start(emulator, wipe_data=True): command = '{0} @{1} {2}'.format(emulator_path, emulator.avd, options) Log.info('Booting {0} with cmd:'.format(emulator.avd)) Log.info(command) - Run.command(cmd=command, wait=False, register_for_cleanup=False) + run(cmd=command, wait=False, register=False) booted = Adb.wait_until_boot(id=emulator.id) if booted: Log.info('{0} is up and running!'.format(emulator.avd)) @@ -102,7 +103,7 @@ class Simulator(object): def create(simulator_info): cmd = 'xcrun simctl create {0} "{1}" com.apple.CoreSimulator.SimRuntime.iOS-{2}' \ .format(simulator_info.name, simulator_info.device_type, str(simulator_info.sdk).replace('.', '-')) - result = Run.command(cmd=cmd, timeout=60) + result = run(cmd=cmd, timeout=60) assert result.exit_code == 0, 'Failed to create iOS Simulator with name {0}'.format(simulator_info.name) assert '-' in result.output, 'Failed to create iOS Simulator with name {0}'.format(simulator_info.name) simulator_info.id = result.output.splitlines()[0] @@ -121,8 +122,8 @@ def stop(id='booted'): Process.kill('launchd_sim') Process.kill_by_commandline('CoreSimulator') else: - print 'Stop simulator with id ' + id - Run.command(cmd='xcrun simctl shutdown {0}'.format(id), timeout=60) + Log.info('Stop simulator with id ' + id) + run(cmd='xcrun simctl shutdown {0}'.format(id), timeout=60) @staticmethod def start(simulator_info): @@ -135,7 +136,7 @@ def start(simulator_info): Log.debug('Simulator GUI is already running.') else: Log.info('Start simulator GUI.') - Run.command(cmd='open -a Simulator') + run(cmd='open -a Simulator') # Return result device = Device(id=simulator_info.id, name=simulator_info.name, type=DeviceType.SIM, diff --git a/core/utils/device/simctl.py b/core/utils/device/simctl.py index 7bca22f0..d5a861f4 100644 --- a/core/utils/device/simctl.py +++ b/core/utils/device/simctl.py @@ -4,8 +4,9 @@ from core.log.log import Log from core.utils.file_utils import File -from core.utils.process import Run, Process +from core.utils.process import Process from core.utils.wait import Wait +from utils.run import run # noinspection PyShadowingBuiltins @@ -14,7 +15,7 @@ class Simctl(object): @staticmethod def __run_simctl_command(command, wait=True, timeout=30): command = '{0} {1}'.format('xcrun simctl', command) - return Run.command(cmd=command, wait=wait, timeout=timeout) + return run(cmd=command, wait=wait, timeout=timeout) # noinspection PyBroadException @staticmethod diff --git a/core/utils/git.py b/core/utils/git.py index be632cdc..12f6a7c0 100644 --- a/core/utils/git.py +++ b/core/utils/git.py @@ -3,7 +3,7 @@ """ from core.settings import Settings from core.utils.file_utils import Folder -from core.utils.process import Run +from utils.run import run def get_repo_url(repo_url, ssh_clone=False): @@ -29,6 +29,6 @@ def clone(repo_url, local_folder, branch=None): command = 'git clone {0} "{1}"'.format(repo_url, str(local_folder)) if branch is not None: command = command + ' -b ' + branch - result = Run.command(cmd=command) + result = run(cmd=command) assert "fatal" not in result.output, "Failed to clone: " + repo_url assert result.exit_code is 0, "Failed to clone: " + repo_url diff --git a/core/utils/gradle.py b/core/utils/gradle.py index 0ec3de88..8689905c 100644 --- a/core/utils/gradle.py +++ b/core/utils/gradle.py @@ -7,7 +7,8 @@ from core.enums.os_type import OSType from core.log.log import Log from core.settings import Settings -from core.utils.process import Run, Process +from core.utils.process import Process +from utils.run import run class Gradle(object): @@ -18,12 +19,12 @@ def kill(): Process.kill(proc_name='java.exe', proc_cmdline='gradle') else: command = "ps -ef | grep '.gradle/wrapper' | grep -v grep | awk '{ print $2 }' | xargs kill -9" - Run.command(cmd=command) + run(cmd=command) @staticmethod def cache_clean(): Log.info("Clean gradle cache.") if Settings.HOST_OS is OSType.WINDOWS: - Run.command(cmd="rmdir /s /q {USERPROFILE}\\.gradle".format(**os.environ)) + run(cmd="rmdir /s /q {USERPROFILE}\\.gradle".format(**os.environ)) else: - Run.command(cmd="rm -rf ~/.gradle") + run(cmd="rm -rf ~/.gradle") diff --git a/core/utils/npm.py b/core/utils/npm.py index 9e5c5978..7caa50b1 100644 --- a/core/utils/npm.py +++ b/core/utils/npm.py @@ -6,8 +6,8 @@ from core.log.log import Log from core.settings import Settings from core.utils.file_utils import File -from core.utils.process import Run from core.utils.version import Version +from utils.run import run class Npm(object): @@ -15,7 +15,7 @@ class Npm(object): def __run_npm_command(cmd, folder=Settings.TEST_RUN_HOME, verify=True): command = 'npm {0}'.format(cmd) Log.info(command) - result = Run.command(cmd=command, cwd=folder) + result = run(cmd=command, cwd=folder, wait=True, timeout=60) if verify: assert result.exit_code is 0, "`npm " + command + "` exited with non zero exit code!: \n" + result.output Log.debug(result.output) diff --git a/core/utils/process.py b/core/utils/process.py index f61f532a..66df3d71 100644 --- a/core/utils/process.py +++ b/core/utils/process.py @@ -1,9 +1,6 @@ import logging import os -import shlex import time -from datetime import datetime -from subprocess import Popen, PIPE import psutil @@ -11,72 +8,6 @@ from core.enums.os_type import OSType from core.log.log import Log from core.settings import Settings -from core.utils.file_utils import File -from core.utils.process_info import ProcessInfo - - -class Run(object): - @staticmethod - def command(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, fail_safe=False, register_for_cleanup=True, timeout=600, - log_level=logging.DEBUG): - # Init result values - log_file = None - complete = False - duration = None - output = '' - - # Command settings - if not wait: - time_string = datetime.now().strftime('%Y_%m_%d_%H_%M_%S') - log_file = os.path.join(Settings.TEST_OUT_LOGS, 'command_{0}.txt'.format(time_string)) - File.write(path=log_file, text=cmd + os.linesep + '====>' + os.linesep) - cmd = cmd + ' >> ' + log_file + ' 2>&1 &' - - # Execute command: - Log.log(level=log_level, message='Execute command: ' + cmd) - Log.log(level=logging.DEBUG, message='CWD: ' + cwd) - if wait: - start = time.time() - if Settings.HOST_OS is OSType.WINDOWS: - process = Popen(cmd, stdout=PIPE, bufsize=1, cwd=cwd, shell=True) - else: - args = shlex.split(cmd) - process = Popen(args, stdout=PIPE, stderr=PIPE, cwd=cwd, shell=False) - p = psutil.Process(process.pid) - - # TODO: On Windows we hang if command do not complete for specified time - # See: https://stackoverflow.com/questions/2408650/why-does-python-subprocess-hang-after-proc-communicate - if Settings.HOST_OS is not OSType.WINDOWS: - try: - p.wait(timeout=timeout) - except psutil.TimeoutExpired: - p.kill() - if not fail_safe: - raise - else: - Log.error('Command reached timeout limit: {0}'.format(cmd)) - out, err = process.communicate() - output = (str(out) + os.linesep + str(err)).rstrip() - complete = True - end = time.time() - duration = end - start - else: - process = Popen(cmd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, cwd=cwd) - - # Get result - pid = process.pid - exit_code = process.returncode - - if wait: - Log.log(level=log_level, message='OUTPUT: ' + os.linesep + output) - else: - Log.log(level=log_level, message='OUTPUT REDIRECTED: ' + log_file) - - result = ProcessInfo(cmd=cmd, pid=pid, exit_code=exit_code, output=output, log_file=log_file, complete=complete, - duration=duration) - if psutil.pid_exists(result.pid) and register_for_cleanup: - TestContext.STARTED_PROCESSES.append(result) - return result # noinspection PyBroadException,PyUnusedLocal @@ -165,10 +96,12 @@ def kill(proc_name, proc_cmdline=None): except Exception: continue if proc_name == name: + if Settings.HOST_OS == OSType.WINDOWS: + cmdline = cmdline.replace('\\\\', '\\') if (proc_cmdline is None) or (proc_cmdline is not None and proc_cmdline in cmdline): try: proc.kill() - Log.log(level=logging.DEBUG, message="Process {0} has been killed.".format(proc_name)) + Log.log(level=logging.DEBUG, msg="Process {0} has been killed.".format(proc_name)) result = True except psutil.NoSuchProcess: continue @@ -186,7 +119,7 @@ def kill_by_commandline(cmdline): if cmdline in cmd: try: proc.kill() - Log.log(level=logging.DEBUG, message="Process {0} has been killed.".format(cmdline)) + Log.log(level=logging.DEBUG, msg="Process {0} has been killed.".format(cmdline)) result = True except psutil.NoSuchProcess: continue @@ -197,7 +130,7 @@ def kill_pid(pid): try: p = psutil.Process(pid) p.terminate() - Log.log(level=logging.DEBUG, message="Process has been killed: {0}{1}".format(os.linesep, p.cmdline())) + Log.log(level=logging.DEBUG, msg="Process has been killed: {0}{1}".format(os.linesep, p.cmdline())) except Exception: pass @@ -207,8 +140,8 @@ def kill_by_handle(file_path): try: for item in proc.open_files(): if file_path in item.path: - print "{0} is locked by {1}".format(file_path, proc.name()) - print "Proc cmd: {0}".format(proc.cmdline()) + Log.debug("{0} is locked by {1}".format(file_path, proc.name())) + Log.debug("Proc cmd: {0}".format(proc.cmdline())) proc.kill() except Exception: continue diff --git a/core/utils/run.py b/core/utils/run.py index f29f23cb..d3698fa7 100644 --- a/core/utils/run.py +++ b/core/utils/run.py @@ -1,7 +1,5 @@ import logging import os -import signal -import sys import time from datetime import datetime @@ -13,7 +11,7 @@ from core.utils.file_utils import File from core.utils.process_info import ProcessInfo -if os.name == 'posix' and sys.version_info[0] < 3: +if os.name == 'posix' and Settings.PYTHON_VERSION < 3: # Import subprocess32 on Posix when Python2 is detected # noinspection PyPackageRequirements import subprocess32 as subprocess @@ -24,26 +22,26 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False, register=True, log_level=logging.DEBUG): # Init result values - log_file = None + time_string = datetime.now().strftime('%Y_%m_%d_%H_%M_%S') + log_file = os.path.join(Settings.TEST_OUT_LOGS, 'command_{0}.txt'.format(time_string)) complete = False duration = None output = '' # Command settings if not wait: - time_string = datetime.now().strftime('%Y_%m_%d_%H_%M_%S') - log_file = os.path.join(Settings.TEST_OUT_LOGS, 'command_{0}.txt'.format(time_string)) File.write(path=log_file, text=cmd + os.linesep + '====>' + os.linesep) cmd = cmd + ' >> ' + log_file + ' 2>&1 &' # Log command that will be executed: - Log.log(level=log_level, message='Execute command: ' + cmd) - Log.log(level=logging.DEBUG, message='CWD: ' + cwd) + Log.log(level=log_level, msg='Execute command: ' + cmd) + Log.log(level=logging.DEBUG, msg='CWD: ' + cwd) # Execute command: if wait: start = time.time() - process = subprocess.Popen(cmd, cwd=cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + with open(log_file, "w") as log: + process = subprocess.Popen(cmd, cwd=cwd, shell=True, stdout=subprocess.PIPE, stderr=log) # Wait until command complete try: @@ -60,11 +58,11 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False Log.error('Command "{0}" timeout after {1} seconds.'.format(cmd, timeout)) else: raise + output = output + File.read(path=log_file) end = time.time() duration = end - start else: - process = psutil.Popen(cmd, cwd=cwd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True, - preexec_fn=os.setsid) + process = psutil.Popen(cmd, cwd=cwd, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True) # Get result pid = process.pid @@ -72,9 +70,9 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False # Log output of the process if wait: - Log.log(level=log_level, message='OUTPUT: ' + os.linesep + output) + Log.log(level=log_level, msg='OUTPUT: ' + os.linesep + output) else: - Log.log(level=log_level, message='OUTPUT REDIRECTED: ' + log_file) + Log.log(level=log_level, msg='OUTPUT REDIRECTED: ' + log_file) # Construct result result = ProcessInfo(cmd=cmd, pid=pid, exit_code=exit_code, output=output, log_file=log_file, complete=complete, diff --git a/core/utils/screen.py b/core/utils/screen.py index 87f440a9..1e73bbb3 100644 --- a/core/utils/screen.py +++ b/core/utils/screen.py @@ -1,25 +1,25 @@ +import logging import os - import time -import pytesseract -from PIL import Image - from core.enums.os_type import OSType from core.settings import Settings -from core.utils.file_utils import File -from core.utils.process import Run +from log.log import Log +from utils.file_utils import File +from utils.image_utils import ImageUtils +from utils.run import run class Screen(object): @staticmethod - def save_screen(path): + def save_screen(path, log_level=logging.DEBUG): """ Save screen of host machine. :param path: Path where screen will be saved. + :param log_level: Log level of the command. """ - print 'Save current host screen at {0}'.format(path) + Log.log(level=log_level, msg='Save current host screen at {0}'.format(path)) if Settings.HOST_OS is OSType.LINUX: import os os.system("import -window root {0}".format(path)) @@ -29,11 +29,10 @@ def save_screen(path): im = ImageGrab.grab() im.save(path) except IOError: - print 'Failed to take screen of host OS' + Log.error('Failed to take screen of host OS') if Settings.HOST_OS is OSType.OSX: - print 'Retry...' - # noinspection SpellCheckingInspection - Run.command(cmd='screencapture ' + path) + Log.info('Retry...') + run(cmd='screencapture ' + path) @staticmethod def get_screen_text(): @@ -45,8 +44,8 @@ def get_screen_text(): if File.exists(actual_image_path): File.clean(actual_image_path) Screen.save_screen(path=actual_image_path) - image = Image.open(actual_image_path) - text = pytesseract.image_to_string(image.convert('L')) + text = ImageUtils.get_text(image_path=actual_image_path) + File.clean(actual_image_path) return text @staticmethod @@ -59,17 +58,16 @@ def wait_for_text(text, timeout=60): """ t_end = time.time() + timeout found = False - actual_text = "" + actual_text = '' while time.time() < t_end: actual_text = Screen.get_screen_text() if text in actual_text: - print text + " found the screen!" + Log.info('"{0}" found on screen.'.format(text)) found = True break else: - print text + " NOT found the screen!" + Log.debug('"{0}" NOT found on screen.'.format(text)) time.sleep(5) if not found: - print "ACTUAL TEXT:" - print actual_text + Log.info('Actual text: {0}{1}'.format(os.linesep, actual_text)) return found diff --git a/core/utils/xcode.py b/core/utils/xcode.py index 81b4f97d..7ee8a4d5 100644 --- a/core/utils/xcode.py +++ b/core/utils/xcode.py @@ -1,8 +1,8 @@ """ A wrapper around Xcode. """ -from core.utils.process import Run from core.utils.version import Version +from utils.run import run class Xcode(object): @@ -11,7 +11,7 @@ def cache_clean(): """ Cleanup Xcode cache and derived data """ - Run.command(cmd="rm -rf ~/Library/Developer/Xcode/DerivedData/*") + run(cmd="rm -rf ~/Library/Developer/Xcode/DerivedData/*") @staticmethod def get_version(): @@ -19,5 +19,5 @@ def get_version(): Get Xcode version :return: Version as int. """ - result = Run.command(cmd='xcodebuild -version').output.splitlines()[0].replace(' ', '').replace('Xcode', '') + result = run(cmd='xcodebuild -version').output.splitlines()[0].replace(' ', '').replace('Xcode', '') return Version.get(result) diff --git a/core_tests/core/adb_tests.py b/core_tests/core/adb_tests.py index 113f01f6..bd96a401 100644 --- a/core_tests/core/adb_tests.py +++ b/core_tests/core/adb_tests.py @@ -1,14 +1,13 @@ import os from core.base_test.tns_test import TnsTest -from core.log.log import Log from core.settings import Settings from core.utils.device.adb import Adb from core.utils.device.device_manager import DeviceManager -# noinspection PyMethodMayBeStatic from core.utils.file_utils import File +# noinspection PyMethodMayBeStatic class AdbTests(TnsTest): emu = None @@ -16,7 +15,7 @@ class AdbTests(TnsTest): def setUpClass(cls): TnsTest.setUpClass() DeviceManager.Emulator.stop() - cls.emu = DeviceManager.Emulator.start(Settings.Emulators.DEFAULT, wipe_data=False) + cls.emu = DeviceManager.Emulator.start(Settings.Emulators.DEFAULT, wipe_data=True) def setUp(self): TnsTest.setUp(self) diff --git a/core_tests/core/device_tests.py b/core_tests/core/device_tests.py index cc4126ad..0fe6641c 100644 --- a/core_tests/core/device_tests.py +++ b/core_tests/core/device_tests.py @@ -1,5 +1,4 @@ import os -import unittest from core.base_test.tns_test import TnsTest from core.enums.device_type import DeviceType diff --git a/core_tests/core/run_tests.py b/core_tests/core/run_tests.py index cc838152..004c2e5e 100644 --- a/core_tests/core/run_tests.py +++ b/core_tests/core/run_tests.py @@ -1,5 +1,4 @@ import os -import platform import time import unittest from os.path import expanduser @@ -13,7 +12,6 @@ # noinspection PyMethodMayBeStatic -@unittest.skipIf('Windows' in platform.platform(), 'This test class can not be exececuted on Windows OS.') class RunPosixTests(unittest.TestCase): def tearDown(self): @@ -23,7 +21,7 @@ def test_01_run_simple_command(self): home = expanduser("~") result = run(cmd='ls ' + home, wait=True, timeout=1) assert result.exit_code == 0, 'Wrong exit code of successful command.' - assert result.log_file is None, 'No log file should be generated if wait=True.' + # assert result.log_file is None, 'No log file should be generated if wait=True.' assert result.complete is True, 'Complete should be true when process execution is complete.' assert result.duration < 1, 'Process duration took too much time.' assert 'Desktop' in result.output, 'Listing home do not include Desktop folder.' @@ -33,7 +31,7 @@ def test_02_run_command_with_redirect(self): out_file = os.path.join(Settings.TEST_OUT_HOME, 'log.txt') result = run(cmd='ls ' + home + ' > ' + out_file, wait=True, timeout=1) assert result.exit_code == 0, 'Wrong exit code of successful command.' - assert result.log_file is None, 'No log file should be generated if wait=True.' + # assert result.log_file is None, 'No log file should be generated if wait=True.' assert result.complete is True, 'Complete should be true when process execution is complete.' assert result.duration < 1, 'Process duration took too much time.' assert result.output == '', 'Output should be empty.' @@ -42,7 +40,7 @@ def test_02_run_command_with_redirect(self): def test_03_run_command_with_pipe(self): result = run(cmd='echo "test case" | wc -w ', wait=True, timeout=1) assert result.exit_code == 0, 'Wrong exit code of successful command.' - assert result.log_file is None, 'No log file should be generated if wait=True.' + # assert result.log_file is None, 'No log file should be generated if wait=True.' assert result.complete is True, 'Complete should be true when process execution is complete.' assert result.duration < 1, 'Process duration took too much time.' assert result.output == '2', 'Output should be 2.' @@ -58,7 +56,7 @@ def test_10_run_command_with_wait_true_that_exceed_timeout(self): def test_11_run_command_with_wait_true_and_fail_safe_that_exceed_timeout(self): result = run(cmd='sleep 3', wait=True, timeout=1, fail_safe=True) assert result.exit_code is None, 'Exit code on non completed programs should be None.' - assert result.log_file is None, 'No log file should be generated if wait=True.' + # assert result.log_file is None, 'No log file should be generated if wait=True.' assert result.complete is False, 'Complete should be true when process execution is complete.' assert result.duration < 2, 'Process duration should be same as timeout.' assert result.output == '', 'No output for not completed programs.' @@ -77,3 +75,13 @@ def test_20_run_long_living_process(self): assert result.log_file is not None, 'stdout and stderr of tail command should be redirected to file.' assert 'tail' in File.read(result.log_file), 'Log file should contains cmd of the command.' assert 'test' in File.read(result.log_file), 'Log file should contains output of the command.' + + @timed(30) + def test_40_run_npm_pack(self): + path = os.path.join(Settings.TEST_SUT_HOME, 'tns-android-5.0.0.tgz') + File.clean(path) + result = run(cmd='npm pack https://registry.npmjs.org/tns-android/-/tns-android-5.0.0.tgz', + cwd=Settings.TEST_SUT_HOME, wait=True) + assert File.exists(path) + assert 'tns-android-5.0.0.tgz' in result.output + assert '=== Tarball Contents ===' in result.output diff --git a/products/nativescript/app.py b/products/nativescript/app.py index 1b3e237c..c632f2e6 100644 --- a/products/nativescript/app.py +++ b/products/nativescript/app.py @@ -4,7 +4,7 @@ from core.settings import Settings from core.utils.json_utils import JsonUtils from core.utils.npm import Npm -from core.utils.process import Run +from utils.run import run class App(object): @@ -53,7 +53,7 @@ def update(app_name, modules=True, angular=True, typescript=True, web_pack=True, Npm.uninstall(package='nativescript-angular', option='--save', folder=app_path) Npm.install(package=Settings.Packages.ANGULAR, option='--save', folder=app_path) update_script = os.path.join(app_path, 'node_modules', '.bin', 'update-app-ng-deps') - result = Run.command(cmd=update_script, log_level=logging.INFO) + result = run(cmd=update_script, log_level=logging.INFO) assert 'Angular dependencies updated' in result.output, 'Angular dependencies not updated.' Npm.install(folder=app_path) if typescript and App.is_dev_dependency(app_name=app_name, dependency='nativescript-dev-typescript'): @@ -63,7 +63,7 @@ def update(app_name, modules=True, angular=True, typescript=True, web_pack=True, Npm.uninstall(package='nativescript-dev-webpack', option='--save-dev', folder=app_path) Npm.install(package=Settings.Packages.WEBPACK, option='--save-dev', folder=app_path) update_script = os.path.join(app_path, 'node_modules', '.bin', 'update-ns-webpack') + ' --deps --configs' - result = Run.command(cmd=update_script, log_level=logging.INFO) + result = run(cmd=update_script, log_level=logging.INFO) assert 'Updating dev dependencies...' in result.output, 'Webpack dependencies not updated.' assert 'Updating configuration files...' in result.output, 'Webpack configs not updated.' Npm.install(folder=app_path) From 1021d8baaee25abd211f69297b5e43709c4a5bc1 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Mon, 3 Dec 2018 16:25:05 +0200 Subject: [PATCH 05/15] chore: articles for opencv with Python3 on Windows --- SETUP.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SETUP.md b/SETUP.md index 9d65e887..1631ce79 100644 --- a/SETUP.md +++ b/SETUP.md @@ -20,6 +20,14 @@ Download [installer](https://github.com/UB-Mannheim/tesseract/wiki) and install Notes: Installation of python wrapper around `tesseract` is handled in `requirements.txt`. +## OpenCV + +OpenCV has some known installation issues on Windows when Python 3.7 is used. + +Please read those articles: +- [import-cv2-doesnt-give-error-on-command-prompt-but-error-on-idle-on-windows-10รณ](https://stackoverflow.com/questions/49516989/import-cv2-doesnt-give-error-on-command-prompt-but-error-on-idle-on-windows-10) +- [opencv-for-python-3-x-under-windows](https://stackoverflow.com/questions/26489867/opencv-for-python-3-x-under-windows) +- [pythonlibs](https://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv) ## (macOS Only) Allow apps to control your computer From b643a6ad402b46490a777f255574849ea254d7e7 Mon Sep 17 00:00:00 2001 From: Dimitar Topuzov Date: Mon, 3 Dec 2018 06:42:58 -0800 Subject: [PATCH 06/15] fix: imports --- core/log/log.py | 2 ++ core/utils/device/adb.py | 2 +- core/utils/device/device.py | 2 +- core/utils/device/device_manager.py | 2 +- core/utils/device/simctl.py | 2 +- core/utils/git.py | 2 +- core/utils/gradle.py | 2 +- core/utils/npm.py | 2 +- core/utils/xcode.py | 2 +- products/nativescript/app.py | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/core/log/log.py b/core/log/log.py index 4b7ca093..c55615e9 100644 --- a/core/log/log.py +++ b/core/log/log.py @@ -51,12 +51,14 @@ def test_end(test_name, outcome): Log.info('TEST COMPLETE: {0}'.format(test_name)) Log.info('OUTCOME: {0}'.format(outcome)) Log.info('=============================================================') + Log.info('') @staticmethod def test_class_end(class_name): Log.info('') Log.info('END CLASS: {0}'.format(class_name)) Log.info('=============================================================') + Log.info('') @staticmethod def test_step(message): diff --git a/core/utils/device/adb.py b/core/utils/device/adb.py index e2ee9586..4e7aef08 100644 --- a/core/utils/device/adb.py +++ b/core/utils/device/adb.py @@ -6,7 +6,7 @@ from core.settings import Settings from core.utils.file_utils import File from core.utils.process import Process -from utils.run import run +from core.utils.run import run ANDROID_HOME = os.environ.get('ANDROID_HOME') ADB_PATH = os.path.join(ANDROID_HOME, 'platform-tools', 'adb') diff --git a/core/utils/device/device.py b/core/utils/device/device.py index 989bf8af..ed50e996 100644 --- a/core/utils/device/device.py +++ b/core/utils/device/device.py @@ -11,8 +11,8 @@ from core.utils.device.simctl import Simctl from core.utils.file_utils import File, Folder from core.utils.image_utils import ImageUtils +from core.utils.run import run from core.utils.wait import Wait -from utils.run import run if Settings.HOST_OS is OSType.OSX: from core.utils.device.simauto import SimAuto diff --git a/core/utils/device/device_manager.py b/core/utils/device/device_manager.py index f1c8eed4..c1437bab 100644 --- a/core/utils/device/device_manager.py +++ b/core/utils/device/device_manager.py @@ -8,7 +8,7 @@ from core.utils.device.idevice import IDevice from core.utils.device.simctl import Simctl from core.utils.process import Process -from utils.run import run +from core.utils.run import run class DeviceManager(object): diff --git a/core/utils/device/simctl.py b/core/utils/device/simctl.py index d5a861f4..e0df6964 100644 --- a/core/utils/device/simctl.py +++ b/core/utils/device/simctl.py @@ -5,8 +5,8 @@ from core.log.log import Log from core.utils.file_utils import File from core.utils.process import Process +from core.utils.run import run from core.utils.wait import Wait -from utils.run import run # noinspection PyShadowingBuiltins diff --git a/core/utils/git.py b/core/utils/git.py index 12f6a7c0..7a22acf5 100644 --- a/core/utils/git.py +++ b/core/utils/git.py @@ -3,7 +3,7 @@ """ from core.settings import Settings from core.utils.file_utils import Folder -from utils.run import run +from core.utils.run import run def get_repo_url(repo_url, ssh_clone=False): diff --git a/core/utils/gradle.py b/core/utils/gradle.py index 8689905c..d1a34e5d 100644 --- a/core/utils/gradle.py +++ b/core/utils/gradle.py @@ -8,7 +8,7 @@ from core.log.log import Log from core.settings import Settings from core.utils.process import Process -from utils.run import run +from core.utils.run import run class Gradle(object): diff --git a/core/utils/npm.py b/core/utils/npm.py index 7caa50b1..b95adfa2 100644 --- a/core/utils/npm.py +++ b/core/utils/npm.py @@ -6,8 +6,8 @@ from core.log.log import Log from core.settings import Settings from core.utils.file_utils import File +from core.utils.run import run from core.utils.version import Version -from utils.run import run class Npm(object): diff --git a/core/utils/xcode.py b/core/utils/xcode.py index 7ee8a4d5..18338781 100644 --- a/core/utils/xcode.py +++ b/core/utils/xcode.py @@ -1,8 +1,8 @@ """ A wrapper around Xcode. """ +from core.utils.run import run from core.utils.version import Version -from utils.run import run class Xcode(object): diff --git a/products/nativescript/app.py b/products/nativescript/app.py index c632f2e6..17da993a 100644 --- a/products/nativescript/app.py +++ b/products/nativescript/app.py @@ -4,7 +4,7 @@ from core.settings import Settings from core.utils.json_utils import JsonUtils from core.utils.npm import Npm -from utils.run import run +from core.utils.run import run class App(object): From 8affaeba9ebac901720a52dcf4248216d382fb6b Mon Sep 17 00:00:00 2001 From: Dimitar Topuzov Date: Mon, 3 Dec 2018 06:48:08 -0800 Subject: [PATCH 07/15] fix: imports --- products/angular/ng.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/products/angular/ng.py b/products/angular/ng.py index f793ee58..26680c7f 100644 --- a/products/angular/ng.py +++ b/products/angular/ng.py @@ -4,7 +4,8 @@ from core.base_test.test_context import TestContext from core.settings import Settings from core.utils.file_utils import File, Folder -from core.utils.process import Run, Process +from core.utils.process import Process +from core.utils.run import run from core.utils.wait import Wait NS_SCHEMATICS = "@nativescript/schematics" @@ -23,7 +24,7 @@ def exec_command(command, cwd=Settings.TEST_RUN_HOME, wait=True): :rtype: core.utils.process_info.ProcessInfo """ cmd = '{0} {1}'.format(Settings.Executables.NG, command) - return Run.command(cmd=cmd, cwd=cwd, wait=wait, log_level=logging.INFO) + return run(cmd=cmd, cwd=cwd, wait=wait, log_level=logging.INFO) @staticmethod def new(collection=NS_SCHEMATICS, project=Settings.AppName.DEFAULT, shared=True, sample=False, prefix=None, From 35c16b485e709332b16ef63bdeec90d93d4997ae Mon Sep 17 00:00:00 2001 From: Dimitar Topuzov Date: Mon, 3 Dec 2018 06:59:01 -0800 Subject: [PATCH 08/15] fix: call nativescript-dev-typescript update script --- products/nativescript/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/products/nativescript/app.py b/products/nativescript/app.py index 17da993a..fd1011ae 100644 --- a/products/nativescript/app.py +++ b/products/nativescript/app.py @@ -46,23 +46,27 @@ def install_dev_dependency(app_name, dependency, version='latest'): @staticmethod def update(app_name, modules=True, angular=True, typescript=True, web_pack=True, ns_plugins=False): app_path = os.path.join(Settings.TEST_RUN_HOME, app_name) + modules_path = os.path.join(app_path, 'node_modules') if modules and App.is_dependency(app_name=app_name, dependency='tns-core-modules'): Npm.uninstall(package='tns-core-modules', option='--save', folder=app_path) Npm.install(package=Settings.Packages.MODULES, option='--save', folder=app_path) if angular and App.is_dependency(app_name=app_name, dependency='nativescript-angular'): Npm.uninstall(package='nativescript-angular', option='--save', folder=app_path) Npm.install(package=Settings.Packages.ANGULAR, option='--save', folder=app_path) - update_script = os.path.join(app_path, 'node_modules', '.bin', 'update-app-ng-deps') + update_script = os.path.join(modules_path, '.bin', 'update-app-ng-deps') result = run(cmd=update_script, log_level=logging.INFO) assert 'Angular dependencies updated' in result.output, 'Angular dependencies not updated.' Npm.install(folder=app_path) if typescript and App.is_dev_dependency(app_name=app_name, dependency='nativescript-dev-typescript'): Npm.uninstall(package='nativescript-dev-typescript', option='--save-dev', folder=app_path) Npm.install(package=Settings.Packages.TYPESCRIPT, option='--save-dev', folder=app_path) + update_script = os.path.join(modules_path, 'nativescript-dev-typescript', 'bin', 'ns-upgrade-tsconfig') + result = run(cmd=update_script, log_level=logging.INFO) + assert 'Adding tns-core-modules path mappings lib' in result.output if web_pack and App.is_dev_dependency(app_name=app_name, dependency='nativescript-dev-webpack'): Npm.uninstall(package='nativescript-dev-webpack', option='--save-dev', folder=app_path) Npm.install(package=Settings.Packages.WEBPACK, option='--save-dev', folder=app_path) - update_script = os.path.join(app_path, 'node_modules', '.bin', 'update-ns-webpack') + ' --deps --configs' + update_script = os.path.join(modules_path, '.bin', 'update-ns-webpack') + ' --deps --configs' result = run(cmd=update_script, log_level=logging.INFO) assert 'Updating dev dependencies...' in result.output, 'Webpack dependencies not updated.' assert 'Updating configuration files...' in result.output, 'Webpack configs not updated.' From 82c6508ecc08bd503a6efdf8edde7e832cb230c8 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Tue, 4 Dec 2018 10:18:36 +0200 Subject: [PATCH 09/15] chore: remove tem log files --- core/utils/npm.py | 2 +- core/utils/run.py | 1 + run_common.py | 9 +++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/utils/npm.py b/core/utils/npm.py index b95adfa2..558d100a 100644 --- a/core/utils/npm.py +++ b/core/utils/npm.py @@ -15,7 +15,7 @@ class Npm(object): def __run_npm_command(cmd, folder=Settings.TEST_RUN_HOME, verify=True): command = 'npm {0}'.format(cmd) Log.info(command) - result = run(cmd=command, cwd=folder, wait=True, timeout=60) + result = run(cmd=command, cwd=folder, wait=True, timeout=300) if verify: assert result.exit_code is 0, "`npm " + command + "` exited with non zero exit code!: \n" + result.output Log.debug(result.output) diff --git a/core/utils/run.py b/core/utils/run.py index d3698fa7..f4f347c1 100644 --- a/core/utils/run.py +++ b/core/utils/run.py @@ -59,6 +59,7 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False else: raise output = output + File.read(path=log_file) + File.clean(path=log_file) end = time.time() duration = end - start else: diff --git a/run_common.py b/run_common.py index bd5abbdf..bd98c132 100644 --- a/run_common.py +++ b/run_common.py @@ -17,6 +17,11 @@ def __cleanup(): """ Wipe TEST_OUT_HOME. """ + Folder.clean(os.path.join(Settings.TEST_RUN_HOME, 'node_modules')) + Folder.clean(Settings.TEST_OUT_HOME) + Folder.create(Settings.TEST_OUT_LOGS) + Folder.create(Settings.TEST_OUT_IMAGES) + DeviceManager.Emulator.stop() if Settings.HOST_OS == OSType.OSX: DeviceManager.Simulator.stop() @@ -25,10 +30,6 @@ def __cleanup(): Tns.kill() Gradle.kill() Gradle.cache_clean() - Folder.clean(os.path.join(Settings.TEST_RUN_HOME, 'node_modules')) - Folder.clean(Settings.TEST_OUT_HOME) - Folder.create(Settings.TEST_OUT_LOGS) - Folder.create(Settings.TEST_OUT_IMAGES) def __get_templates(): From 3c2941ef71369dba5aac8a57fc285f8c4182a0d0 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Tue, 4 Dec 2018 16:16:43 +0200 Subject: [PATCH 10/15] refactor: run and get_text methods --- core/utils/device/device.py | 8 ++++++-- core/utils/run.py | 7 ++++++- core_tests/{core => device}/adb_tests.py | 0 core_tests/{core => device}/device_tests.py | 0 core_tests/{core => utils}/image_tests.py | 0 core_tests/{core => utils}/process_tests.py | 11 ++++++----- core_tests/{core => utils}/run_tests.py | 2 +- 7 files changed, 19 insertions(+), 9 deletions(-) rename core_tests/{core => device}/adb_tests.py (100%) rename core_tests/{core => device}/device_tests.py (100%) rename core_tests/{core => utils}/image_tests.py (100%) rename core_tests/{core => utils}/process_tests.py (92%) rename core_tests/{core => utils}/run_tests.py (99%) diff --git a/core/utils/device/device.py b/core/utils/device/device.py index ed50e996..c73e387f 100644 --- a/core/utils/device/device.py +++ b/core/utils/device/device.py @@ -57,7 +57,7 @@ def is_text_visible(self, text): # Retry find with ORC if macOS automation fails if not is_visible: - actual_text = self.get_text().encode('utf-8').strip() + actual_text = self.get_text() if text in actual_text: is_visible = True else: @@ -70,7 +70,11 @@ def get_text(self): actual_image_path = os.path.join(Settings.TEST_OUT_IMAGES, img_name) File.clean(actual_image_path) self.get_screen(path=actual_image_path, log_level=logging.DEBUG) - return ImageUtils.get_text(image_path=actual_image_path) + text = ImageUtils.get_text(image_path=actual_image_path) + if Settings.PYTHON_VERSION < 3: + return text.encode('utf-8').strip() + else: + return text.strip() def wait_for_text(self, text, timeout=30, retry_delay=1): t_end = time.time() + timeout diff --git a/core/utils/run.py b/core/utils/run.py index f4f347c1..588b8d43 100644 --- a/core/utils/run.py +++ b/core/utils/run.py @@ -59,7 +59,12 @@ def run(cmd, cwd=Settings.TEST_RUN_HOME, wait=True, timeout=600, fail_safe=False else: raise output = output + File.read(path=log_file) - File.clean(path=log_file) + # noinspection PyBroadException + try: + File.clean(path=log_file) + except Exception: + Log.debug('Failed to clean log file: {0}'.format(log_file)) + log_file = None end = time.time() duration = end - start else: diff --git a/core_tests/core/adb_tests.py b/core_tests/device/adb_tests.py similarity index 100% rename from core_tests/core/adb_tests.py rename to core_tests/device/adb_tests.py diff --git a/core_tests/core/device_tests.py b/core_tests/device/device_tests.py similarity index 100% rename from core_tests/core/device_tests.py rename to core_tests/device/device_tests.py diff --git a/core_tests/core/image_tests.py b/core_tests/utils/image_tests.py similarity index 100% rename from core_tests/core/image_tests.py rename to core_tests/utils/image_tests.py diff --git a/core_tests/core/process_tests.py b/core_tests/utils/process_tests.py similarity index 92% rename from core_tests/core/process_tests.py rename to core_tests/utils/process_tests.py index 07e15c74..fe8591af 100644 --- a/core_tests/core/process_tests.py +++ b/core_tests/utils/process_tests.py @@ -12,8 +12,9 @@ from core.settings import Settings from core.utils.file_utils import File from core.utils.perf_utils import PerfUtils -from core.utils.process import Run, Process +from core.utils.process import Process from core.utils.wait import Wait +from utils.run import run # noinspection PyMethodMayBeStatic @@ -27,7 +28,7 @@ def tearDown(self): def test_01_run_simple_command(self): home = expanduser("~") - result = Run.command(cmd='ls ' + home) + result = run(cmd='ls ' + home) assert result.exit_code == 0, 'Wrong exit code of successful command.' assert result.log_file is None, 'No log file should be generated if wait=True.' assert result.complete is True, 'Complete should be true when process execution is complete.' @@ -37,7 +38,7 @@ def test_01_run_simple_command(self): @timed(5) def test_02_run_command_without_wait_for_completion(self): if Settings.HOST_OS == OSType.WINDOWS: - result = Run.command(cmd='pause', wait=False) + result = run(cmd='pause', wait=False) time.sleep(1) assert result.exit_code is None, 'exit code should be None when command is not complete.' assert result.complete is False, 'pause command should not exit.' @@ -49,7 +50,7 @@ def test_02_run_command_without_wait_for_completion(self): else: file_path = os.path.join(Settings.TEST_OUT_HOME, 'temp.txt') File.write(path=file_path, text='test') - result = Run.command(cmd='tail -f ' + file_path, wait=False) + result = run(cmd='tail -f ' + file_path, wait=False) time.sleep(1) assert result.exit_code is None, 'exit code should be None when command is not complete.' assert result.complete is False, 'tail command should not exit.' @@ -67,7 +68,7 @@ def test_10_wait(self): @timed(5) def test_20_get_average_time(self): - ls_time = PerfUtils.get_average_time(lambda: Run.command(cmd='ifconfig'), retry_count=5) + ls_time = PerfUtils.get_average_time(lambda: run(cmd='ifconfig'), retry_count=5) assert 0.005 <= ls_time <= 0.025, "Command not executed in acceptable time. Actual value: " + str(ls_time) @staticmethod diff --git a/core_tests/core/run_tests.py b/core_tests/utils/run_tests.py similarity index 99% rename from core_tests/core/run_tests.py rename to core_tests/utils/run_tests.py index 004c2e5e..6982ae9d 100644 --- a/core_tests/core/run_tests.py +++ b/core_tests/utils/run_tests.py @@ -12,7 +12,7 @@ # noinspection PyMethodMayBeStatic -class RunPosixTests(unittest.TestCase): +class RunTests(unittest.TestCase): def tearDown(self): Process.kill_all_in_context() From 942c4fd9bee881dd0a8edc9324fbe8feb6d84e51 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Tue, 4 Dec 2018 18:09:10 +0200 Subject: [PATCH 11/15] fix: retry tns commands on 502 error Sometimes we fail to build android because of "Received status code 502 from server: Bad Gateway". Detect such cases and retry once. --- products/nativescript/tns.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/products/nativescript/tns.py b/products/nativescript/tns.py index 18f308db..843e91f9 100644 --- a/products/nativescript/tns.py +++ b/products/nativescript/tns.py @@ -9,6 +9,7 @@ from core.utils.file_utils import Folder, File from core.utils.process import Process from core.utils.run import run +from log.log import Log from products.nativescript.app import App from products.nativescript.tns_assert import TnsAssert @@ -74,7 +75,15 @@ def exec_command(command, cwd=Settings.TEST_RUN_HOME, platform=Platform.NONE, em cmd += ' --justlaunch' if log_trace: cmd += ' --log trace' - return run(cmd=cmd, cwd=cwd, wait=wait, log_level=logging.INFO, timeout=timeout) + + result = run(cmd=cmd, cwd=cwd, wait=wait, log_level=logging.INFO, timeout=timeout) + + # Retry in case of connectivity issues + if result.output is not None and 'Bad Gateway' in result.output: + Log.info('"Bad Gateway" issue detected! Will retry the command ...') + result = run(cmd=cmd, cwd=cwd, wait=wait, log_level=logging.INFO, timeout=timeout) + + return result @staticmethod def create(app_name=Settings.AppName.DEFAULT, template=None, path=None, app_id=None, From fd8ff21528f49fbd190c6571f42e0066443d6e86 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Tue, 4 Dec 2018 21:41:50 +0200 Subject: [PATCH 12/15] reafactor: speed up adb.get_screen() --- core/utils/device/adb.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/utils/device/adb.py b/core/utils/device/adb.py index 4e7aef08..de7d021a 100644 --- a/core/utils/device/adb.py +++ b/core/utils/device/adb.py @@ -149,9 +149,7 @@ def get_screen(id, file_path): if Settings.OSType == OSType.WINDOWS: Adb.__run_adb_command(command='exec-out screencap -p > ' + file_path, id=id, log_level=logging.DEBUG) else: - Adb.__run_adb_command(command='shell rm /sdcard/screen.png', id=id) - Adb.__run_adb_command(command='shell screencap -p /sdcard/screen.png', id=id) - Adb.pull(id=id, source='/sdcard/screen.png', target=file_path) + Adb.__run_adb_command(command="shell screencap -p | perl -pe 's/\\x0D\\x0A/\\x0A/g' > " + file_path, id=id) if File.exists(file_path): return else: From c6455319269db7c887290bd39638f176db7f8839 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Tue, 4 Dec 2018 22:02:27 +0200 Subject: [PATCH 13/15] chore: do not use flaky --- run_ns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_ns.py b/run_ns.py index c86c23e1..3c748ef3 100644 --- a/run_ns.py +++ b/run_ns.py @@ -8,7 +8,7 @@ if __name__ == '__main__': run_common.prepare() Log.info("Running tests...") - arguments = ['nosetests', '-v', '-s', '--nologcapture', '--logging-filter=nose', '--with-xunit', '--with-flaky'] + arguments = ['nosetests', '-v', '-s', '--nologcapture', '--with-doctest', '--with-xunit'] for i in sys.argv: arguments.append(str(i)) nose.run(argv=arguments) From c76cb9e67b8cd3ea20e57337605000b1cc6eb634 Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Wed, 5 Dec 2018 12:58:18 +0200 Subject: [PATCH 14/15] fix: adb.get_screen() --- core/utils/device/adb.py | 2 +- core/utils/device/device.py | 5 +---- core/utils/device/simctl.py | 24 +++++++----------------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/core/utils/device/adb.py b/core/utils/device/adb.py index de7d021a..d0a20133 100644 --- a/core/utils/device/adb.py +++ b/core/utils/device/adb.py @@ -146,7 +146,7 @@ def is_text_visible(id, text, case_sensitive=False): @staticmethod def get_screen(id, file_path): File.clean(path=file_path) - if Settings.OSType == OSType.WINDOWS: + if Settings.HOST_OS == OSType.WINDOWS: Adb.__run_adb_command(command='exec-out screencap -p > ' + file_path, id=id, log_level=logging.DEBUG) else: Adb.__run_adb_command(command="shell screencap -p | perl -pe 's/\\x0D\\x0A/\\x0A/g' > " + file_path, id=id) diff --git a/core/utils/device/device.py b/core/utils/device/device.py index c73e387f..71409f8e 100644 --- a/core/utils/device/device.py +++ b/core/utils/device/device.py @@ -71,10 +71,7 @@ def get_text(self): File.clean(actual_image_path) self.get_screen(path=actual_image_path, log_level=logging.DEBUG) text = ImageUtils.get_text(image_path=actual_image_path) - if Settings.PYTHON_VERSION < 3: - return text.encode('utf-8').strip() - else: - return text.strip() + return text.encode('utf-8').strip() def wait_for_text(self, text, timeout=30, retry_delay=1): t_end = time.time() + timeout diff --git a/core/utils/device/simctl.py b/core/utils/device/simctl.py index e0df6964..91526869 100644 --- a/core/utils/device/simctl.py +++ b/core/utils/device/simctl.py @@ -4,36 +4,26 @@ from core.log.log import Log from core.utils.file_utils import File -from core.utils.process import Process from core.utils.run import run -from core.utils.wait import Wait # noinspection PyShadowingBuiltins class Simctl(object): @staticmethod - def __run_simctl_command(command, wait=True, timeout=30): + def __run_simctl_command(command, wait=True, timeout=60): command = '{0} {1}'.format('xcrun simctl', command) return run(cmd=command, wait=wait, timeout=timeout) # noinspection PyBroadException @staticmethod def __get_simulators(): - result = Simctl.__run_simctl_command(command='list --json devices', wait=False) - logs = result.log_file - found = Wait.until(lambda: 'iPhone' in File.read(logs), timeout=30) - Process.kill_pid(result.pid) - if found: - json_content = '{' + File.read(logs).split('{', 1)[-1] - try: - return json.loads(json_content) - except ValueError: - Log.error('Failed to parse json ' + os.linesep + json_content) - return json.loads('{}') - else: - Log.error(File.read(logs)) - raise Exception('Failed to list iOS Devices!') + result = Simctl.__run_simctl_command(command='list --json devices') + try: + return json.loads(result.output) + except ValueError: + Log.error('Failed to parse json ' + os.linesep + result.output) + return json.loads('{}') @staticmethod def start(simulator_info): From da10ff5bc9865b868b2448584c36a3b2349f120a Mon Sep 17 00:00:00 2001 From: dtopuzov Date: Wed, 5 Dec 2018 13:25:16 +0200 Subject: [PATCH 15/15] fix: get text on python 3 --- core/utils/device/device.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/utils/device/device.py b/core/utils/device/device.py index 71409f8e..9d6a6063 100644 --- a/core/utils/device/device.py +++ b/core/utils/device/device.py @@ -71,7 +71,10 @@ def get_text(self): File.clean(actual_image_path) self.get_screen(path=actual_image_path, log_level=logging.DEBUG) text = ImageUtils.get_text(image_path=actual_image_path) - return text.encode('utf-8').strip() + if Settings.PYTHON_VERSION < 3: + return text.encode('utf-8').strip() + else: + return text.encode('utf-8').strip().decode('utf-8') def wait_for_text(self, text, timeout=30, retry_delay=1): t_end = time.time() + timeout