forked from jcreigh/pylabview
-
Notifications
You must be signed in to change notification settings - Fork 25
/
test_readRSRC_llb.py
239 lines (221 loc) · 13.4 KB
/
test_readRSRC_llb.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
232
233
234
235
236
237
238
239
# -*- coding: utf-8 -*-
""" Test for pyLabview project, readRSRC script.
This test extracts and then re-creates some RSRC files.
Run it using `pytest` in project root folder.
"""
# Copyright (C) 2022 Mefistotelis <[email protected]>
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.
import filecmp
import glob
import itertools
import logging
import os
import re
import sys
import pathlib
import pytest
import xml.etree.ElementTree as ET
from unittest.mock import patch
# Import the functions to be tested
from pylabview.readRSRC import main as readRSRC_main
LOGGER = logging.getLogger(__name__)
@pytest.mark.parametrize("rsrc_inp_fn", [fn for fn in itertools.chain.from_iterable([ glob.glob(e, recursive=True) for e in (
'./examples/**/*.vi',
'./examples/**/*.ctl',
'./examples/**/*.vit',
'./examples/**/*.mnu',
'./examples/**/*.ctt',
'./examples/**/*.uir',
'./examples/**/*.lsb',
'./examples/**/*.rsc',
) ]) if os.path.isfile(fn)] )
def test_readRSRC_repack_vi(rsrc_inp_fn):
""" Test extraction and re-creation of VI/CTL/RSC files.
VI files generated by the tool should be (with some exceptions, ie. older files which use
random values for padding, or LLB files) same as original on binary level. Extracting and
re-packing such file should result in receiving a binary-identical copy of the file.
"""
# Most files we are able to recreate with full accuracy
expect_file_identical = True
# Only some files can be successfully tested in Python < 3.8, as XML parser was
# improved in that version to preserve order of attributes.
if sys.version_info < (3,8):
if (
re.match(r'^.*lv071[/\\]vi.lib[/\\]express[/\\]rvi[/\\]timing[/\\]WaitPropertyPage[.]vi$', rsrc_inp_fn) or
False):
pytest.skip("this file will not produce comparable binary in python <= 3.8")
elif (
re.match(r'^.*lv100[/\\]resource[/\\]provers[.]rsc$', rsrc_inp_fn) or
False):
expect_file_identical = True
else:
LOGGER.warning("Expected non-identical binary in python <= 3.8: {:s}".format(rsrc_inp_fn))
expect_file_identical = False
# Some files have strings in unpredicatable order, adjust for that.
if (
re.match(r'^.*lv040[/\\]lvstring[.]rsc$', rsrc_inp_fn) or
re.match(r'^.*lv040[/\\]menus[/\\]default[/\\]anmeas[.]mnu$', rsrc_inp_fn) or
False):
LOGGER.warning("Expected non-identical binary due to random strings order: {:s}".format(rsrc_inp_fn))
expect_file_identical = False
# Some files have some indexes in unpredicatable order, adjust for that.
if (
re.match(r'^.*lv100[/\\]menus[/\\]Controls[/\\]High Color[/\\]3dio[.]mnu$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]menus[/\\]Controls[/\\]High Color[/\\]dir[.]mnu$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]menus[/\\]Controls[/\\]Low Color[/\\]dir[.]mnu$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]menus[/\\]Controls[/\\]Low Color[/\\]io[.]mnu$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]menus[/\\]Controls[/\\]System[/\\]dir[.]mnu$', rsrc_inp_fn) or
False):
LOGGER.warning("Expected non-identical binary due to random indexes order: {:s}".format(rsrc_inp_fn))
expect_file_identical = False
# Some files have sections belonging to different blocks interleaved; the tool currently keeps all
# sections from a block together, so original order of sections cannot be preserved. It often concerns
# the ICON sections; anyway adjust for that.
if (
re.match(r'^.*lv040[/\\]user.lib[/\\]dir[.]mnu$', rsrc_inp_fn) or
re.match(r'^.*lv071[/\\]user.lib[/\\]dir[.]mnu$', rsrc_inp_fn) or
False):
LOGGER.warning("Expected non-identical binary due to sections interleaving: {:s}".format(rsrc_inp_fn))
expect_file_identical = False
# Some files have section data ordered with different rules than what the tool uses; not sure if the order is predictable.
if (
re.match(r'^.*lv100[/\\]menus[/\\]default[/\\]root[.]mnu$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]menus[/\\]default[/\\]_shared[/\\]picture[.]mnu$', rsrc_inp_fn) or
False):
LOGGER.warning("Expected non-identical binary due to sections ordering rules: {:s}".format(rsrc_inp_fn))
expect_file_identical = False
# Some files have random values in padding. In re-created files, padding is always with zeros.
if (
re.match(r'^.*lv071[/\\]vi.lib[/\\]_probes[/\\]Pixmap Probe[.]vi$', rsrc_inp_fn) or # only 1 byte different
re.match(r'^.*lv071[/\\]vi.lib[/\\]express[/\\]express shared[/\\]ex_TableInputs[.]vi$', rsrc_inp_fn) or # 5 bytes different
re.match(r'^.*lv071[/\\]vi.lib[/\\]express[/\\]rvi[/\\]timing[/\\]WaitPropertyPage[.]vi$', rsrc_inp_fn) or # 1 byte different
False):
LOGGER.warning("Expected non-identical binary due to non-zeros in padding: {:s}".format(rsrc_inp_fn))
expect_file_identical = False
rsrc_path, rsrc_filename = os.path.split(rsrc_inp_fn)
rsrc_path = pathlib.Path(rsrc_path)
rsrc_basename, rsrc_fileext = os.path.splitext(rsrc_filename)
xml_fn = "{:s}.xml".format(rsrc_basename)
if len(rsrc_path.parts) > 1:
rsrc_out_path = os.sep.join(["test_out"] + list(rsrc_path.parts[1:]))
else:
rsrc_out_path = "test_out"
rsrc_out_fn = os.sep.join([rsrc_out_path, "{:s}{:s}".format(rsrc_basename, rsrc_fileext)])
single_vi_path_extr1 = os.sep.join([rsrc_out_path, "{:s}_extr1".format(rsrc_basename)])
if not os.path.exists(single_vi_path_extr1):
os.makedirs(single_vi_path_extr1)
# Extract the VI file
command = [os.path.join("pylabview", "readRSRC.py"), "-vv", "-x", "-i", rsrc_inp_fn, "-m", os.sep.join([single_vi_path_extr1, xml_fn])]
with patch.object(sys, 'argv', command):
readRSRC_main()
# Re-create the VI file
command = [os.path.join("pylabview", "readRSRC.py"), "-vv", "-c", "-m", os.sep.join([single_vi_path_extr1, xml_fn]), "-i", rsrc_out_fn]
with patch.object(sys, 'argv', command):
readRSRC_main()
if expect_file_identical:
# Compare repackaged file and the original byte-to-byte
match = filecmp.cmp(rsrc_inp_fn, rsrc_out_fn, shallow=False)
assert match, "Re-created file different: {:s}".format(rsrc_inp_fn)
else:
# Check if repackaged file size roughly matches the original
rsrc_inp_fsize = os.path.getsize(rsrc_inp_fn)
rsrc_out_fsize = os.path.getsize(rsrc_out_fn)
assert rsrc_out_fsize >= int(rsrc_inp_fsize * 0.95), "Re-created file too small: {:s}".format(rsrc_inp_fn)
assert rsrc_out_fsize <= int(rsrc_inp_fsize * 1.05), "Re-created file too large: {:s}".format(rsrc_inp_fn)
@pytest.mark.parametrize("rsrc_inp_fn", [fn for fn in itertools.chain.from_iterable([ glob.glob(e, recursive=True) for e in (
'./examples/**/*.llb',
) ]) if os.path.isfile(fn)] )
def test_readRSRC_repack_llb(rsrc_inp_fn):
""" Test extraction and re-creation of LLB files.
LLB files generated by the tool are NOT the same as original on binary level. That is because
names section generation has time dependencies (not to mention in some old versions of LV, the
padding is often filled with random data). So instead of comparing LLBs, compare the extracted
files from first extraction and second extraction.
"""
# Only some files can be successfully tested in Python < 3.8, as XML parser was
# improved in that version to preserve order of attributes.
if sys.version_info < (3,8):
if (
re.match(r'^.*blank_project1_extr_from_exe_lv14f1[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv040[/\\]demos[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv040[/\\]startapp[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]vi.lib[/\\]express[/\\]express analysis[/\\]Convolution-CorrConfig[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]vi.lib[/\\]express[/\\]express analysis[/\\]ToneConfig[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]vi.lib[/\\]picture[/\\]3D Picture Control[/\\]Old 3D Toolkit[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv100[/\\]examples[/\\]measure[/\\]maxmpl[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv060[/\\]vi.lib[/\\]platform[/\\]_sersup[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv060[/\\]vi.lib[/\\]GMath[/\\]2dexplo[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv071[/\\]vi.lib[/\\]instr[/\\]_sersup[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv140[/\\]vi.lib[/\\]express[/\\]express analysis[/\\]Convolution-CorrConfig[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv140[/\\]vi.lib[/\\]express[/\\]express analysis[/\\]DistortionConfig[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv140[/\\]vi.lib[/\\]express[/\\]express analysis[/\\]MaskLimitConfig[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv140[/\\]vi.lib[/\\]Platform[/\\]lvScriptEngine[.]llb$', rsrc_inp_fn) or
re.match(r'^.*lv140[/\\]vi.lib[/\\]addons[/\\]internet[/\\]url[/\\]url[.]llb$', rsrc_inp_fn) or
False):
pass
else:
pytest.skip("this file will not produce comparable binary in python <= 3.8")
rsrc_path, rsrc_filename = os.path.split(rsrc_inp_fn)
rsrc_path = pathlib.Path(rsrc_path)
rsrc_basename, rsrc_fileext = os.path.splitext(rsrc_filename)
xml_fn = "{:s}.xml".format(rsrc_basename)
if len(rsrc_path.parts) > 1:
rsrc_out_path = os.sep.join(["test_out"] + list(rsrc_path.parts[1:]))
else:
rsrc_out_path = "test_out"
rsrc_out_fn = os.sep.join([rsrc_out_path, "{:s}{:s}".format(rsrc_basename, rsrc_fileext)])
single_vi_path_extr1 = os.sep.join([rsrc_out_path, "{:s}_extr1".format(rsrc_basename)])
if not os.path.exists(single_vi_path_extr1):
os.makedirs(single_vi_path_extr1)
single_vi_path_extr2 = os.sep.join([rsrc_out_path, "{:s}_extr2".format(rsrc_basename)])
if not os.path.exists(single_vi_path_extr2):
os.makedirs(single_vi_path_extr2)
# Extract the LLB file
command = [os.path.join("pylabview", "readRSRC.py"), "-vv", "-x", "--keep-names", "-i", rsrc_inp_fn, "-m", os.sep.join([single_vi_path_extr1, xml_fn])]
with patch.object(sys, 'argv', command):
readRSRC_main()
# Re-create the LLB file
command = [os.path.join("pylabview", "readRSRC.py"), "-vv", "-c", "-m", os.sep.join([single_vi_path_extr1, xml_fn]), "-i", rsrc_out_fn]
with patch.object(sys, 'argv', command):
readRSRC_main()
# Check if re-created file size roughly matches the original
rsrc_inp_fsize = os.path.getsize(rsrc_inp_fn)
rsrc_out_fsize = os.path.getsize(rsrc_out_fn)
assert rsrc_out_fsize >= int(rsrc_inp_fsize * 0.95), "Re-created file too small: {:s}".format(rsrc_inp_fn)
assert rsrc_out_fsize <= int(rsrc_inp_fsize * 1.05), "Re-created file too large: {:s}".format(rsrc_inp_fn)
# Re-extract the LLB file
command = [os.path.join("pylabview", "readRSRC.py"), "-vv", "-x", "--keep-names", "-i", rsrc_out_fn, "-m", os.sep.join([single_vi_path_extr2, xml_fn])]
with patch.object(sys, 'argv', command):
readRSRC_main()
# Compare files from first extraction to the ones from second extraction
dirs_cmp = filecmp.dircmp(single_vi_path_extr1, single_vi_path_extr2)
assert len(dirs_cmp.left_only) == 0, "Files exist only in 1st extraction: {:s}".format(', '.join(dirs_cmp.left_only))
assert len(dirs_cmp.right_only) == 0, "Files exist only in 2nd extraction: {:s}".format(', '.join(dirs_cmp.right_only))
assert len(dirs_cmp.funny_files) == 0, "Some funny files encountered: {:s}".format(', '.join(dirs_cmp.funny_files))
(match, mismatch, errors) = filecmp.cmpfiles(single_vi_path_extr1, single_vi_path_extr2, dirs_cmp.common_files, shallow=False)
# Ignore some expected differences
if len(mismatch) > 0:
# Some LLB files contain ordering of items not supported by pylabview, resulting in different order of items in re-extracted XML
# Ignore such XML differences, as it was a design decision to not care for ordering in such cases
if xml_fn in mismatch:
LOGGER.warning("XML not identical - ignoring expected ordering changes in extraction from: {}".format(rsrc_inp_fn))
tree_extr1 = ET.parse(os.sep.join([single_vi_path_extr1, xml_fn]))
tree_extr2 = ET.parse(os.sep.join([single_vi_path_extr2, xml_fn]))
for root in [tree_extr1.getroot(), tree_extr2.getroot()]:
for elem in root.findall('SpecialOrder'):
root.remove(elem)
if hasattr( ET, "canonicalize" ):
canonic_extr1 = ET.canonicalize(ET.tostring(tree_extr1.getroot()), strip_text=True)
canonic_extr2 = ET.canonicalize(ET.tostring(tree_extr2.getroot()), strip_text=True)
else:
LOGGER.warning("The old Python does not have ET.canonicalize(), skipping")
canonic_extr1 = ET.tostring(tree_extr1.getroot())
canonic_extr2 = ET.tostring(tree_extr2.getroot())
assert canonic_extr1 == canonic_extr2, "Re-extracted XML different even after ordering ignore: {:s}".format(xml_fn)
mismatch = [fn for fn in mismatch if not fn.endswith(xml_fn)]
assert len(mismatch) == 0, "Re-extracted files different: {:s}".format(', '.join(mismatch))
assert len(errors) == 0, "Errors reading files: {:s}".format(', '.join(errors))
# We should have an XML file and at least one extracted section file
assert len(match) >= 2