/
check_opi_format.py
231 lines (178 loc) · 10.4 KB
/
check_opi_format.py
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
import os
import sys
import argparse
import unittest
from lxml import etree
from lxml.etree import LxmlError
from check_OPI_format_utils.colour_checker import check_colour, check_plot_area_backgrounds
from check_OPI_format_utils.text import check_label_punctuation, check_label_case_inside_containers, \
check_label_case_outside_containers
from check_OPI_format_utils.container import get_items_not_in_grouping_container
from check_OPI_format_utils.font import get_incorrect_fonts
from check_OPI_format_utils.position import get_widgets_outside_of_boundary
from xmlrunner import XMLTestRunner
from check_OPI_format_utils.xy_graph import get_traces_with_different_buffer_sizes, get_trigger_pv
from check_opi_format_tests import TestCheckOpiFormatMethods
# Directory to iterate through
DEFAULT_ROOT_DIR = r"."
# Specify a logs directory
DEFAULT_LOGS_DIR = r"./check_OPI_format_logs/"
def file_iterator(directory, file_name=None):
"""
Generator that returns the opi files that should be checked.
Args:
directory (str): The root directory to check for OPIs
file_name (str): The name of the single file to check, every OPI in root_dir is checked if this is None
Yields:
str: Path to the OPI that must be checked
"""
if file_name is None:
# No single file was defined - iterate through all files
for path, _, files in os.walk(directory):
for file_name in files:
if file_name.endswith(".opi"):
yield os.path.join(path, file_name)
else:
# Only check one file
yield os.path.join(directory, file_name)
class CheckStrictOpiFormat(unittest.TestCase):
"""
These are the tests run in CI.
"""
IGNORED_OPIS = ["dma4500m", "/stage\\", "sdtest", "qepro", "/pinhole_selector\\", "/gateway\\", "/autosave\\",
"/asyn\\", "template", "scimag3D", "analyser", "attenuator", "polariser", "rotating_bench",
"/SKFG5Chopper\\"]
# Need the 'None' default because unittest's test loader uses a
# no-argument constructor when getting the test names.
def __init__(self, methodName, xml_root=None):
# Boilerplate so that unittest knows how to run these tests.
super(CheckStrictOpiFormat, self).__init__(methodName=methodName)
self.xml_root = xml_root
def _assert_colour_correct(self, location, widget, colours):
errors = check_colour(self.xml_root, widget, location, colours)
if len(errors):
self.fail("\n".join(["On line {}, text '{}', colour was not correct.".format(*error) for error in errors]))
def _assert_trace_buffers_are_the_same(self):
errors = get_traces_with_different_buffer_sizes(self.xml_root)
if len(errors):
self.fail("\n".join(["On line {}, buffer size {}, was different to the first, {}, in same graph xy widget."
.format(*error) for error in errors]))
def test_GIVEN_an_opi_file_with_grouping_containers_WHEN_checking_the_background_colour_THEN_it_is_the_isis_background(self):
self._assert_colour_correct("background_color", "groupingContainer", ["ISIS_OPI_Background"])
def test_GIVEN_an_opi_file_with_labels_WHEN_checking_the_background_colour_THEN_it_is_the_isis_background(self):
self._assert_colour_correct("background_color", "Label", ["ISIS_Label_Background", "ISIS_Title_Background_NEW"])
def test_GIVEN_an_opi_file_with_textbox_WHEN_checking_background_colour_THEN_it_is_the_isis_textbox_background(self):
self._assert_colour_correct("background_color", "TextInput", ["ISIS_Textbox_Background"])
def test_GIVEN_an_opi_file_with_xygraph_WHEN_checking_background_colour_THEN_it_is_the_isis_background(self):
self._assert_colour_correct("background_color", "xyGraph", ["ISIS_OPI_Background"])
def test_GIVEN_an_opi_file_with_textbox_WHEN_checking_foreground_colour_THEN_it_is_the_isis_textbox_foreground(self):
self._assert_colour_correct("foreground_color", "TextInput", ["ISIS_Standard_Text"])
def test_GIVEN_an_opi_file_with_led_WHEN_checking_on_colour_THEN_it_is_the_isis_led_on_colour(self):
self._assert_colour_correct("on_color", "LED", ["ISIS_Green_LED_On", "ISIS_Red_LED_On"])
def test_GIVEN_an_opi_file_with_led_WHEN_checking_off_colour_THEN_it_is_the_isis_led_off_colour(self):
self._assert_colour_correct("off_color", "LED", ["ISIS_Green_LED_Off", "ISIS_Red_LED_Off"])
def test_GIVEN_plot_area_THEN_it_has_correct_plot_area_background_colour(self):
errors = check_plot_area_backgrounds(self.xml_root)
if len(errors):
message = "\n".join(["Plot on line {} with name '{}' has incorrect plot area background colour"
.format(*error) for error in errors])
self.fail(message)
def test_GIVEN_plot_area_THEN_it_has_a_trigger_PV(self):
errors = get_trigger_pv(self.xml_root)
if len(errors):
message = "\n".join(["Plot on line {} has no trigger PV, it should be triggered on a heartbeat"
.format(*error) for error in errors])
self.fail(message)
class CheckOpiFormat(CheckStrictOpiFormat):
def _assert_widgets_inside_x_y_boundary(self):
errors = get_widgets_outside_of_boundary(self.xml_root)
if len(errors):
self.fail("\n".join(["On line {}, widget '{}', outside of boundary with position {}."
.format(*error) for error in errors]))
def test_GIVEN_an_opi_file_with_widgets_WHEN_checking_if_widget_within_boundaries_THEN_widget_is_within_boundaries(self):
self._assert_widgets_inside_x_y_boundary()
def test_GIVEN_an_opi_file_with_graph_widgets_WHEN_checking_buffer_sizes_THEN_all_buffer_sizes_are_the_same(self):
self._assert_trace_buffers_are_the_same()
def test_GIVEN_a_label_THEN_it_ends_in_a_colon(self):
errors = check_label_punctuation(self.xml_root)
if len(errors):
message = "\n".join(["Label on line {} with text '{}' did not end in a colon."
.format(*error) for error in errors])
self.fail(message)
def test_GIVEN_a_label_within_a_grouping_container_THEN_it_is_sentence_case(self):
errors = check_label_case_inside_containers(self.xml_root)
if len(errors):
message = "\n".join(["Label on line {} with text '{}' is not sentence case"
.format(*error) for error in errors])
self.fail(message)
def test_GIVEN_a_label_outside_a_grouping_container_THEN_it_is_title_case(self):
errors = check_label_case_outside_containers(self.xml_root)
if len(errors):
message = "\n".join(["Label on line {} with text '{}' is not title case"
.format(*error) for error in errors])
self.fail(message)
def _assert_widget_not_outside_container(self, widget):
errors = get_items_not_in_grouping_container(self.xml_root, widget)
if len(errors):
message = "\n".join(["{} on line {} ({}) was not in a grouping container"
.format(widget, *error) for error in errors])
self.fail(message)
def test_GIVEN_a_push_button_THEN_it_is_within_a_grouping_container(self):
self._assert_widget_not_outside_container("NativeButton")
def test_GIVEN_a_LED_THEN_it_is_within_a_grouping_container(self):
self._assert_widget_not_outside_container("LED")
def test_GIVEN_a_combo_THEN_it_is_within_a_grouping_container(self):
self._assert_widget_not_outside_container("combo")
def test_GIVEN_a_TextInput_THEN_it_is_within_a_grouping_container(self):
self._assert_widget_not_outside_container("TextInput")
def test_GIVEN_a_label_THEN_it_has_correct_font(self):
errors = get_incorrect_fonts(self.xml_root)
if len(errors):
message = "\n".join(["Label on line {} with text '{}' has incorrect font"
.format(*error) for error in errors])
self.fail(message)
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog='check_opi_format')
parser.add_argument('-file', type=str, default=None,
help='A single file to check')
parser.add_argument('-directory', type=str, default=DEFAULT_ROOT_DIR,
help='A directory to check files in')
parser.add_argument('-logs_directory', type=str, default=DEFAULT_LOGS_DIR,
help='A directory to save the logs into')
parser.add_argument('-strict', action="store_true", default=False,
help="Run only the strict tests")
args = parser.parse_args()
single_file = args.file
root_dir = args.directory
logs_dir = args.logs_directory
loader = unittest.TestLoader()
def self_valid():
self_test_suite = unittest.TestSuite()
self_test_suite.addTests(loader.loadTestsFromTestCase(TestCheckOpiFormatMethods))
runner = XMLTestRunner(output=os.path.join(logs_dir, "check_opi_format"), stream=sys.stdout)
return runner.run(self_test_suite).wasSuccessful()
if not self_valid():
print("Check OPI format test script failed own tests. Aborting")
sys.exit(1)
return_values = []
xml_parser = etree.XMLParser(remove_blank_text=True)
# Add test suite a dynamic number of times with an argument.
# unittest's test loader is unable to take arguments to test classes by default so have
# to use the getTestCaseNames() syntax and explicitly add the argument ourselves.
for filename in file_iterator(root_dir, single_file):
print("Testing '{}'".format(filename))
suite = unittest.TestSuite()
try:
root = etree.parse(filename, xml_parser)
except LxmlError as e:
print("XML failed to parse {}".format(e))
return_values.append(False)
continue
if args.strict:
if not any(opi in filename for opi in CheckStrictOpiFormat.IGNORED_OPIS):
suite.addTests([CheckStrictOpiFormat(test, root) for test in loader.getTestCaseNames(CheckStrictOpiFormat)])
else:
suite.addTests([CheckOpiFormat(test, root) for test in loader.getTestCaseNames(CheckOpiFormat)])
runner = XMLTestRunner(output=os.path.join(logs_dir, filename), stream=sys.stdout)
return_values.append(runner.run(suite).wasSuccessful())
sys.exit(False in return_values)