Skip to content

Commit 2cbedc6

Browse files
authored
refactor: run command (#1)
1 parent 5a366df commit 2cbedc6

27 files changed

+355
-194
lines changed

README.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,41 @@
44

55
Project with test for NativeScript tooling.
66

7-
## Install Requirements
7+
## Requirements
8+
9+
**Posix:**
10+
- Python 2.7 or Python 3.2+
11+
12+
**Windows**
13+
- Python 3.2+
814

9-
Install Python 2.*:
10-
```
11-
brew install python
12-
```
15+
16+
## Before Running Tests
17+
18+
**Install Required Packages**
1319

1420
Update `pip` and install project requirements:
1521
```
1622
python -m pip install --upgrade pip
17-
pip install -r requirements.txt --user
1823
```
1924

20-
## Before Running Tests
25+
Install packages on macOS:
26+
```bash
27+
pip install --upgrade -r requirements_darwin.txt --user
28+
```
29+
Install packages on Windows on Linux:
30+
```bash
31+
pip install --upgrade -r requirements.txt --user
32+
```
33+
34+
Set `PYTHONUNBUFFERED` and `PYTHONIOENCODING` environment variables:
35+
```bash
36+
export PYTHONUNBUFFERED=1
37+
export PYTHONIOENCODING=utf-8
38+
```
39+
Notes:
40+
- `PYTHONUNBUFFERED` is required to get logging on Jenkins CI working properly.
41+
- `PYTHONIOENCODING` helps to get command execution more stable.
2142

2243
**Setup Machine**
2344

SETUP.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Install Tesseract
44

5-
In order to get OCR features workign you need to install `tesseract`.
5+
In order to get OCR features working you need to install `tesseract`.
66

77
**macOS**
88
```bash
@@ -20,6 +20,14 @@ Download [installer](https://github.com/UB-Mannheim/tesseract/wiki) and install
2020
Notes:
2121
Installation of python wrapper around `tesseract` is handled in `requirements.txt`.
2222

23+
## OpenCV
24+
25+
OpenCV has some known installation issues on Windows when Python 3.7 is used.
26+
27+
Please read those articles:
28+
- [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)
29+
- [opencv-for-python-3-x-under-windows](https://stackoverflow.com/questions/26489867/opencv-for-python-3-x-under-windows)
30+
- [pythonlibs](https://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv)
2331

2432
## (macOS Only) Allow apps to control your computer
2533

core/base_test/tns_test.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ def setUpClass(cls):
2727
Tns.kill()
2828
Gradle.kill()
2929
TnsTest.kill_emulators()
30-
cls.kill_processes()
3130

3231
# Ensure log folders are create
3332
Folder.create(Settings.TEST_OUT_HOME)
@@ -50,10 +49,16 @@ def setUp(self):
5049
def tearDown(self):
5150
# Kill processes
5251
Tns.kill()
53-
self.kill_processes()
52+
Process.kill_all_in_context()
5453

5554
# Analise test result
56-
result = self._resultForDoCleanups
55+
if Settings.PYTHON_VERSION < 3:
56+
# noinspection PyUnresolvedReferences
57+
result = self._resultForDoCleanups
58+
else:
59+
# noinspection PyUnresolvedReferences
60+
result = self._outcome.result
61+
5762
outcome = 'FAILED'
5863
if result.errors == [] and result.failures == []:
5964
outcome = 'PASSED'
@@ -69,18 +74,9 @@ def tearDownClass(cls):
6974
"""
7075
Tns.kill()
7176
TnsTest.kill_emulators()
72-
for process in TestContext.STARTED_PROCESSES:
73-
Log.info("Kill Process: " + os.linesep + process.commandline)
74-
Process.kill_pid(process.pid)
77+
Process.kill_all_in_context()
7578
Log.test_class_end(class_name=cls.__name__)
7679

77-
@staticmethod
78-
def kill_processes():
79-
for process in TestContext.STARTED_PROCESSES:
80-
if Process.is_running(process.pid):
81-
Log.info("Kill Process: " + os.linesep + process.commandline)
82-
Process.kill_pid(process.pid)
83-
8480
@staticmethod
8581
def kill_emulators():
8682
DeviceManager.Emulator.stop()

core/log/log.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
class Log(object):
99

1010
@staticmethod
11-
def log(level, message):
11+
def log(level, msg):
1212
if level != logging.DEBUG:
1313
date = datetime.datetime.now().strftime('%H:%M:%S')
14-
print '{0} {1}'.format(date, message)
14+
print('{0} {1}'.format(date, msg))
1515

1616
@staticmethod
1717
def debug(message):
@@ -51,12 +51,14 @@ def test_end(test_name, outcome):
5151
Log.info('TEST COMPLETE: {0}'.format(test_name))
5252
Log.info('OUTCOME: {0}'.format(outcome))
5353
Log.info('=============================================================')
54+
Log.info('')
5455

5556
@staticmethod
5657
def test_class_end(class_name):
5758
Log.info('')
5859
Log.info('END CLASS: {0}'.format(class_name))
5960
Log.info('=============================================================')
61+
Log.info('')
6062

6163
@staticmethod
6264
def test_step(message):

core/settings/Settings.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import os
33
import platform
4+
import sys
45

56
from core.enums.env import EnvironmentType
67
from core.enums.os_type import OSType
@@ -17,6 +18,10 @@ def get_os():
1718
return OSType.LINUX
1819

1920

21+
def get_python_version():
22+
return sys.version_info[0]
23+
24+
2025
def get_env():
2126
env = os.environ.get('TEST_ENV', 'next')
2227
if 'next' in env:
@@ -34,14 +39,14 @@ def get_project_home():
3439
return home
3540

3641

42+
HOST_OS = get_os()
43+
PYTHON_VERSION = get_python_version()
44+
ENV = get_env()
45+
3746
LOG_LEVEL = logging.DEBUG
3847

3948
NS_GIT_ORG = 'NativeScript'
4049

41-
HOST_OS = get_os()
42-
43-
ENV = get_env()
44-
4550
TEST_RUN_HOME = get_project_home()
4651
TEST_SUT_HOME = os.path.join(TEST_RUN_HOME, 'sut')
4752

core/utils/device/adb.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from core.enums.os_type import OSType
66
from core.settings import Settings
77
from core.utils.file_utils import File
8-
from core.utils.process import Run, Process
8+
from core.utils.process import Process
9+
from core.utils.run import run
910

1011
ANDROID_HOME = os.environ.get('ANDROID_HOME')
1112
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,
1920
command = '{0} {1}'.format(ADB_PATH, command)
2021
else:
2122
command = '{0} -s {1} {2}'.format(ADB_PATH, id, command)
22-
return Run.command(cmd=command, wait=wait, timeout=timeout, fail_safe=fail_safe, log_level=log_level)
23+
return run(cmd=command, wait=wait, timeout=timeout, fail_safe=fail_safe, log_level=log_level)
2324

2425
@staticmethod
2526
def __get_ids(include_emulator=False):
@@ -145,12 +146,10 @@ def is_text_visible(id, text, case_sensitive=False):
145146
@staticmethod
146147
def get_screen(id, file_path):
147148
File.clean(path=file_path)
148-
if Settings.OSType == OSType.WINDOWS:
149-
Adb.__run_adb_command(command='exec-out screencap -p > ' + file_path, id=id, log_level=logging.INFO)
149+
if Settings.HOST_OS == OSType.WINDOWS:
150+
Adb.__run_adb_command(command='exec-out screencap -p > ' + file_path, id=id, log_level=logging.DEBUG)
150151
else:
151-
Adb.__run_adb_command(command='shell rm /sdcard/screen.png', id=id)
152-
Adb.__run_adb_command(command='shell screencap -p /sdcard/screen.png', id=id)
153-
Adb.pull(id=id, source='/sdcard/screen.png', target=file_path)
152+
Adb.__run_adb_command(command="shell screencap -p | perl -pe 's/\\x0D\\x0A/\\x0A/g' > " + file_path, id=id)
154153
if File.exists(file_path):
155154
return
156155
else:

core/utils/device/device.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from core.utils.device.simctl import Simctl
1212
from core.utils.file_utils import File, Folder
1313
from core.utils.image_utils import ImageUtils
14-
from core.utils.process import Run
14+
from core.utils.run import run
1515
from core.utils.wait import Wait
1616

1717
if Settings.HOST_OS is OSType.OSX:
@@ -26,7 +26,7 @@ def __init__(self, id, name, type, version):
2626
self.version = version
2727

2828
if type is DeviceType.IOS:
29-
type = Run.command(cmd="ideviceinfo | grep ProductType")
29+
type = run(cmd="ideviceinfo | grep ProductType")
3030
type = type.replace(',', '')
3131
type = type.replace('ProductType:', '').strip(' ')
3232
self.name = type
@@ -57,7 +57,7 @@ def is_text_visible(self, text):
5757

5858
# Retry find with ORC if macOS automation fails
5959
if not is_visible:
60-
actual_text = self.get_text().encode('utf-8').strip()
60+
actual_text = self.get_text()
6161
if text in actual_text:
6262
is_visible = True
6363
else:
@@ -70,7 +70,11 @@ def get_text(self):
7070
actual_image_path = os.path.join(Settings.TEST_OUT_IMAGES, img_name)
7171
File.clean(actual_image_path)
7272
self.get_screen(path=actual_image_path, log_level=logging.DEBUG)
73-
return ImageUtils.get_text(image_path=actual_image_path)
73+
text = ImageUtils.get_text(image_path=actual_image_path)
74+
if Settings.PYTHON_VERSION < 3:
75+
return text.encode('utf-8').strip()
76+
else:
77+
return text.encode('utf-8').strip().decode('utf-8')
7478

7579
def wait_for_text(self, text, timeout=30, retry_delay=1):
7680
t_end = time.time() + timeout
@@ -114,7 +118,7 @@ def get_screen(self, path, log_level=logging.INFO):
114118
image_saved = True
115119
if image_saved:
116120
message = "Image of {0} saved at {1}".format(self.id, path)
117-
Log.log(level=log_level, message=message)
121+
Log.log(level=log_level, msg=message)
118122
else:
119123
message = "Failed to save image of {0} saved at {1}".format(self.id, path)
120124
Log.error(message)

core/utils/device/device_manager.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from core.utils.device.device import Device
88
from core.utils.device.idevice import IDevice
99
from core.utils.device.simctl import Simctl
10-
from core.utils.process import Run, Process
10+
from core.utils.process import Process
11+
from core.utils.run import run
1112

1213

1314
class DeviceManager(object):
@@ -59,7 +60,7 @@ def start(emulator, wipe_data=True):
5960
command = '{0} @{1} {2}'.format(emulator_path, emulator.avd, options)
6061
Log.info('Booting {0} with cmd:'.format(emulator.avd))
6162
Log.info(command)
62-
Run.command(cmd=command, wait=False, register_for_cleanup=False)
63+
run(cmd=command, wait=False, register=False)
6364
booted = Adb.wait_until_boot(id=emulator.id)
6465
if booted:
6566
Log.info('{0} is up and running!'.format(emulator.avd))
@@ -102,7 +103,7 @@ class Simulator(object):
102103
def create(simulator_info):
103104
cmd = 'xcrun simctl create {0} "{1}" com.apple.CoreSimulator.SimRuntime.iOS-{2}' \
104105
.format(simulator_info.name, simulator_info.device_type, str(simulator_info.sdk).replace('.', '-'))
105-
result = Run.command(cmd=cmd, timeout=60)
106+
result = run(cmd=cmd, timeout=60)
106107
assert result.exit_code == 0, 'Failed to create iOS Simulator with name {0}'.format(simulator_info.name)
107108
assert '-' in result.output, 'Failed to create iOS Simulator with name {0}'.format(simulator_info.name)
108109
simulator_info.id = result.output.splitlines()[0]
@@ -121,8 +122,8 @@ def stop(id='booted'):
121122
Process.kill('launchd_sim')
122123
Process.kill_by_commandline('CoreSimulator')
123124
else:
124-
print 'Stop simulator with id ' + id
125-
Run.command(cmd='xcrun simctl shutdown {0}'.format(id), timeout=60)
125+
Log.info('Stop simulator with id ' + id)
126+
run(cmd='xcrun simctl shutdown {0}'.format(id), timeout=60)
126127

127128
@staticmethod
128129
def start(simulator_info):
@@ -135,7 +136,7 @@ def start(simulator_info):
135136
Log.debug('Simulator GUI is already running.')
136137
else:
137138
Log.info('Start simulator GUI.')
138-
Run.command(cmd='open -a Simulator')
139+
run(cmd='open -a Simulator')
139140

140141
# Return result
141142
device = Device(id=simulator_info.id, name=simulator_info.name, type=DeviceType.SIM,

core/utils/device/simctl.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,26 @@
44

55
from core.log.log import Log
66
from core.utils.file_utils import File
7-
from core.utils.process import Run, Process
8-
from core.utils.wait import Wait
7+
from core.utils.run import run
98

109

1110
# noinspection PyShadowingBuiltins
1211
class Simctl(object):
1312

1413
@staticmethod
15-
def __run_simctl_command(command, wait=True, timeout=30):
14+
def __run_simctl_command(command, wait=True, timeout=60):
1615
command = '{0} {1}'.format('xcrun simctl', command)
17-
return Run.command(cmd=command, wait=wait, timeout=timeout)
16+
return run(cmd=command, wait=wait, timeout=timeout)
1817

1918
# noinspection PyBroadException
2019
@staticmethod
2120
def __get_simulators():
22-
result = Simctl.__run_simctl_command(command='list --json devices', wait=False)
23-
logs = result.log_file
24-
found = Wait.until(lambda: 'iPhone' in File.read(logs), timeout=30)
25-
Process.kill_pid(result.pid)
26-
if found:
27-
json_content = '{' + File.read(logs).split('{', 1)[-1]
28-
try:
29-
return json.loads(json_content)
30-
except ValueError:
31-
Log.error('Failed to parse json ' + os.linesep + json_content)
32-
return json.loads('{}')
33-
else:
34-
Log.error(File.read(logs))
35-
raise Exception('Failed to list iOS Devices!')
21+
result = Simctl.__run_simctl_command(command='list --json devices')
22+
try:
23+
return json.loads(result.output)
24+
except ValueError:
25+
Log.error('Failed to parse json ' + os.linesep + result.output)
26+
return json.loads('{}')
3627

3728
@staticmethod
3829
def start(simulator_info):

core/utils/git.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
from core.settings import Settings
55
from core.utils.file_utils import Folder
6-
from core.utils.process import Run
6+
from core.utils.run import run
77

88

99
def get_repo_url(repo_url, ssh_clone=False):
@@ -29,6 +29,6 @@ def clone(repo_url, local_folder, branch=None):
2929
command = 'git clone {0} "{1}"'.format(repo_url, str(local_folder))
3030
if branch is not None:
3131
command = command + ' -b ' + branch
32-
result = Run.command(cmd=command)
32+
result = run(cmd=command)
3333
assert "fatal" not in result.output, "Failed to clone: " + repo_url
3434
assert result.exit_code is 0, "Failed to clone: " + repo_url

0 commit comments

Comments
 (0)