-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathutils.py
More file actions
145 lines (129 loc) · 6.03 KB
/
utils.py
File metadata and controls
145 lines (129 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""
Stuff that's shared across all the various tests.
"""
import json
import logging
import pathlib
import subprocess
import sys
from datetime import timedelta, datetime, date
from typing import Optional
# This will be the standard logging format for all the logging in the tests.
HANDLER: logging.Handler = logging.StreamHandler(sys.stdout)
pfmt = logging.Formatter(
'%(levelname)s\t%(name)s.%(funcName)s:\t%(msg)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
HANDLER.setFormatter(pfmt)
####################
# The following are wrappers around the IOM utility functions so that we'll get consistent results
####################
def str_to_bool(val: str) -> bool:
"""
Return the boolean value of the supplied string. We call IPH3 to do the conversion so we're sure
that the boolean is what the IOM would generate. The script encodes in JSON then unencodes the result is decoded
so that we don't have to eval() it.
:param val: a string representing a boolean value
:return: a JSON string representing the opposite boolean value
"""
script: str = f"""
import utils, json, logging
indigo.server.log(f"str_to_bool('{val}')", type="TestingBase", level=logging.DEBUG);
return json.dumps(utils.str_to_bool('{val}'))
"""
return json.loads(run_host_script(script))
def reverse_bool_str_value(val: str) -> str:
"""
Return the opposite boolean string value of the supplied string. We call IPH3 to do the conversion so we're sure
that the value is what the IOM would generate. The script encodes in JSON then unencodes the result is decoded
so that we don't have to eval() it.
:param val: a string representing a boolean value
:return: a JSON string representing the opposite boolean value
"""
script: str = f"""
import utils, json, logging
indigo.server.log(f"reverse_bool_str_value('{val}')", type="TestingBase", level=logging.DEBUG);
return json.dumps(utils.reverse_bool_str_value('{val}'))
"""
return json.loads(run_host_script(script))
def get_install_folder() -> pathlib.PosixPath:
"""
Return the installation folder path for the running Indigo Server.
:return: the posix path to the installation folder
"""
script: str = """
import logging
indigo.server.log("getting install folder", type="TestingBase", level=logging.DEBUG);
return indigo.server.getInstallFolderPath()
"""
return pathlib.PosixPath(run_host_script(script))
def run_host_script(script: str) -> str:
"""
This will run the given script in the local IndigoPluginHost3 process and return the result. The reply will always
be a string, so unless what you are calling returns a string, you should JSON encode it then decode it when this
function returns.
:param script: string containing the IOM script to run
:return: string containing the result
"""
result: subprocess.CompletedProcess = subprocess.run(
["/usr/local/indigo/indigo-host", "-e", script],
stdout=subprocess.PIPE
)
return result.stdout.decode("utf8").strip("\n")
def within_time_tolerance(datetime1, datetime2, tolerance_seconds: int = 1, raise_exc: bool = False) -> bool:
"""Check if two datetime objects are within specified seconds of each other"""
if not isinstance(datetime1, datetime):
datetime1 = datetime.fromisoformat(datetime1)
if not isinstance(datetime2, datetime):
datetime2 = datetime.fromisoformat(datetime2)
return_val = abs(datetime1 - datetime2) <= timedelta(seconds=tolerance_seconds)
if raise_exc:
raise AssertionError(datetime1, datetime2, "time comparison failed")
return return_val
def compare_dicts(
dict1: dict, dict2: dict,
exclude_keys: Optional[list] = None) -> bool:
"""
This function checks if two dictionaries are equal. You can explicitly exclude certain keys and you can specify
a list of datetime instances and specify a time tolerance in seconds.
:param dict1: a dictionary, usually the dictionary that was generated through operations
:param dict2: a dictionary, usually one created manually for testing purposes which is the expected results
:param exclude_keys: a list of keys to exclude from comparison
:param datetime_tolerance_list: a list of field names to compare as datetime instances within a tolerance in seconds
:return: bool
"""
if exclude_keys is not None:
# First, create two filtered versions of the dicts and compare - if they aren't the same we can just return
# False since we don't need to do anything else.
filtered_dict1 = {k: v for k, v in dict1.items() if k not in exclude_keys}
filtered_dict2 = {k: v for k, v in dict2.items() if k not in exclude_keys}
return filtered_dict1 == filtered_dict2
else:
return dict1 == dict2
class JSONDateEncoder(json.JSONEncoder):
""" This encoder class will convert a python datetime and date objects to strings before
the actual JSON encoding. It has to be done or encoding any datetime objects will
fail. It will also encode a NaN (not a number) object into None since the default
encoder mishandles it.
This is a copy of the IndigoJSONEncoder class from indigo.utils.
"""
def default(self, obj) -> object:
"""
Check to see if the object to be converted is a datetime.datetime or a
datetime.date object, and if so return the ISO date string for it. Also, if the
object is one of the Indigo constants, we create a full string representation
that looks something like this: "indigo.kHvacMode.Off" which can later be
reconstituted to the class by using eval() on it.
Otherwise, call the normal conversion.
:param obj: object to convert
:return: converted object
"""
if isinstance(obj, (date, datetime)):
# return the ISO string for the date or datetime object
return obj.isoformat()
elif math.isnan(obj): # noqa
return None
# otherwise, let the default encoder do it's work
return super().default(obj)
class SoftAssertionWarning(UserWarning):
pass