1
|
|
|
"""Test suite for saveswap.py |
2
|
|
|
|
3
|
|
|
As this relies on helpers from py.test, it must be run with ``py.test``. |
4
|
|
|
""" |
5
|
|
|
|
6
|
|
|
from __future__ import (absolute_import, division, print_function, |
7
|
|
|
with_statement, unicode_literals) |
8
|
|
|
|
9
|
|
|
__author__ = "Stephan Sokolow" |
10
|
|
|
__license__ = "MIT" |
11
|
|
|
__appname__ = "N64-Saveswap" |
12
|
|
|
__version__ = "0.0pre0" |
13
|
|
|
|
14
|
|
|
import os, sys |
15
|
|
|
from contextlib import contextmanager |
16
|
|
|
|
17
|
|
|
from saveswap import (calculate_padding, byteswap, main, process_path, |
18
|
|
|
FileIncomplete, FileTooBig) |
19
|
|
|
|
20
|
|
|
@contextmanager |
21
|
|
|
def set_argv(args): |
22
|
|
|
"""Context manager to temporarily modify sys.argv""" |
23
|
|
|
old_argv = sys.argv |
24
|
|
|
try: |
25
|
|
|
sys.argv = [sys.argv[0]] + [str(x) for x in args] |
26
|
|
|
yield |
27
|
|
|
finally: |
28
|
|
|
sys.argv = old_argv |
29
|
|
|
|
30
|
|
|
def test_calculate_padding(tmpdir): |
31
|
|
|
"""Test that calculate_padding works as expected""" |
32
|
|
|
import pytest |
33
|
|
|
test_file = tmpdir.join("fake_dump") |
34
|
|
|
|
35
|
|
|
for start, expected in ( |
36
|
|
|
(100, 512), (500, 2048), (1000, 32768), (10000, 131072)): |
37
|
|
|
test_file.write("1234" * start) |
38
|
|
|
assert calculate_padding(str(test_file)) == expected |
39
|
|
|
|
40
|
|
|
test_file.write("1234" * 100000) |
41
|
|
|
with pytest.raises(FileTooBig): |
42
|
|
|
calculate_padding(str(test_file)) |
43
|
|
|
|
44
|
|
|
def test_byteswap(tmpdir): |
45
|
|
|
"""Test that byteswap produces the expected output""" |
46
|
|
|
test_file = tmpdir.join("fake_dump") |
47
|
|
|
|
48
|
|
|
# Test the various modes |
49
|
|
|
for options, expected in ( |
50
|
|
|
({}, "4321"), |
51
|
|
|
({'swap_bytes': False}, "3412"), |
52
|
|
|
({'swap_words': False}, "2143"), |
53
|
|
|
({'swap_bytes': False, 'swap_words': False}, "1234")): |
54
|
|
|
test_file.write("1234" * 10) |
55
|
|
|
byteswap(str(test_file), **options) |
56
|
|
|
assert test_file.read() == expected * 10 |
57
|
|
|
|
58
|
|
|
def test_byteswap_padding(tmpdir): |
59
|
|
|
"""Test that byteswap pads as intended""" |
60
|
|
|
test_file = tmpdir.join("fake_dump") |
61
|
|
|
test_file.write("1234" * 100000) |
62
|
|
|
byteswap(str(test_file), pad_to=500000) |
63
|
|
|
assert test_file.read() == ("4321" * 100000) + ("\x00" * 100000) |
64
|
|
|
|
65
|
|
|
def test_byteswap_with_incomplete(tmpdir): |
66
|
|
|
"""Test that byteswap reacts properly to file sizes with remainders |
67
|
|
|
|
68
|
|
|
(ie. file sizes that are not evenly divisible by 2 or 4) |
69
|
|
|
""" |
70
|
|
|
import pytest |
71
|
|
|
test_file = tmpdir.join("fake_dump") |
72
|
|
|
|
73
|
|
|
# Define a function which will be called for each combination of inputs |
74
|
|
|
def test_callback(_bytes, _words, pad_to): |
75
|
|
|
"""Function called many times by _vary_check_swap_inputs""" |
76
|
|
|
# Test that both types of swapping error out on odd-numbered lengths |
77
|
|
|
test_file.write("12345") |
78
|
|
|
if _bytes or _words: |
79
|
|
|
with pytest.raises(FileIncomplete): |
80
|
|
|
byteswap(str(test_file), _bytes, _words, pad_to) |
81
|
|
|
|
82
|
|
|
test_file.write("123456") |
83
|
|
|
if _words: |
84
|
|
|
with pytest.raises(FileIncomplete): |
85
|
|
|
byteswap(str(test_file), _bytes, _words, pad_to) |
86
|
|
|
else: |
87
|
|
|
byteswap(str(test_file), False, _words, pad_to) |
88
|
|
|
|
89
|
|
|
# Let _vary_check_swap_inputs call test_callback once for each combination |
90
|
|
|
_vary_check_swap_inputs(test_callback) |
91
|
|
|
|
92
|
|
|
def test_process_path_autopad_error(tmpdir): |
93
|
|
|
"""Test that process_path reacts to pad_to=None properly on error""" |
94
|
|
|
import pytest |
95
|
|
|
test_file = tmpdir.join("fake_dump") |
96
|
|
|
backup_path = str(test_file) + '.bak' |
97
|
|
|
|
98
|
|
|
test_file.write("1234" * 100000) |
99
|
|
|
assert not os.path.exists(backup_path) |
100
|
|
|
with pytest.raises(FileTooBig): |
101
|
|
|
process_path(str(test_file), pad_to=None) |
102
|
|
|
assert test_file.read() == "1234" * 100000 # Unchanged on error |
103
|
|
|
assert not os.path.exists(backup_path) # No backup on oversize |
104
|
|
|
|
105
|
|
|
def test_process_path_padding(tmpdir): |
106
|
|
|
"""Test that process_path pads properly""" |
107
|
|
|
import pytest |
108
|
|
|
test_file = tmpdir.join("fake_dump") |
109
|
|
|
backup_path = str(test_file) + '.bak' |
110
|
|
|
|
111
|
|
|
test_file.write("1234" * 100000) |
112
|
|
|
process_path(str(test_file), pad_to=500000) |
113
|
|
|
assert test_file.read() == ("4321" * 100000) + ("\x00" * 100000) |
114
|
|
|
assert os.path.exists(backup_path) |
115
|
|
|
|
116
|
|
|
def test_process_path_nopad(tmpdir): |
117
|
|
|
"""Test that process_path reacts to pad_to=0 properly""" |
118
|
|
|
import pytest |
119
|
|
|
test_file = tmpdir.join("fake_dump") |
120
|
|
|
backup_path = str(test_file) + '.bak' |
121
|
|
|
|
122
|
|
|
test_file.write("1234" * 100000) |
123
|
|
|
process_path(str(test_file), pad_to=0) |
124
|
|
|
assert test_file.read() == "4321" * 100000 |
125
|
|
|
assert os.path.exists(backup_path) |
126
|
|
|
|
127
|
|
|
def check_main_retcode(args, code): |
128
|
|
|
"""Helper for testing return codes from main()""" |
129
|
|
|
try: |
130
|
|
|
with set_argv(args): |
131
|
|
|
main() |
132
|
|
|
except SystemExit as err: |
133
|
|
|
assert err.code == code |
134
|
|
|
|
135
|
|
|
def test_main_works(tmpdir): |
136
|
|
|
"""Functional test for basic main() use""" |
137
|
|
|
test_file = tmpdir.join("fake_dump") |
138
|
|
|
backup_path = str(test_file) + '.bak' |
139
|
|
|
|
140
|
|
|
# Test successful runs |
141
|
|
|
for pat_reps, options, expect_pat, expect_len, backup in ( |
142
|
|
|
(500, [], '4321', 2048, False), |
143
|
|
|
(100, ['--swap-mode=words-only'], '3412', 512, True), |
144
|
|
|
(1000, ['--swap-mode=bytes-only'], '2143', 32768, False), |
145
|
|
|
(1000, ['--force-padding=0', |
146
|
|
|
'--swap-mode=bytes-only'], '2143', 4000, False), |
147
|
|
|
(100000, ['--force-padding=500000'], '4321', 500000, True)): |
148
|
|
|
|
149
|
|
|
bkopt = [] if backup else ['--no-backup'] |
150
|
|
|
test_file.write("1234" * pat_reps) |
151
|
|
|
with set_argv(options + bkopt + [test_file]): |
152
|
|
|
main() |
153
|
|
|
assert test_file.read() == (expect_pat * pat_reps) + ( |
154
|
|
|
"\x00" * (expect_len - (4 * pat_reps))) |
155
|
|
|
if backup: |
156
|
|
|
assert os.path.exists(backup_path) |
157
|
|
|
os.remove(backup_path) |
158
|
|
|
|
159
|
|
|
def test_main_missing_file(tmpdir): |
160
|
|
|
"""Functional test for main() with nonexistant path""" |
161
|
|
|
missing_path = str(tmpdir.join("missing_file")) |
162
|
|
|
check_main_retcode([missing_path], 10) |
163
|
|
|
assert not os.path.exists(missing_path + '.bak') |
164
|
|
|
|
165
|
|
|
def test_main_error_returns(tmpdir): |
166
|
|
|
"""Functional test for main() with erroring input""" |
167
|
|
|
test_file = tmpdir.join("fake_dump") |
168
|
|
|
backup_path = str(test_file) + '.bak' |
169
|
|
|
|
170
|
|
|
assert not os.path.exists(backup_path) |
171
|
|
|
test_file.write("1234" * 100000) # Too big |
172
|
|
|
check_main_retcode([test_file], 20) |
173
|
|
|
assert not os.path.exists(backup_path) |
174
|
|
|
|
175
|
|
|
test_file.write("12345") # Not evenly disible by 2 |
176
|
|
|
check_main_retcode([test_file], 30) |
177
|
|
|
# TODO: Fix the code so the backup is removed on this failure |
178
|
|
|
|
179
|
|
|
def _vary_check_swap_inputs(callback): |
180
|
|
|
"""Helper to avoid duplicating stuff within test_byteswap_with_incomplete |
181
|
|
|
|
182
|
|
|
You want to be careful about this, because the number of tests run goes up |
183
|
|
|
exponentially, but with small numbers of combinations, it's very useful. |
184
|
|
|
""" |
185
|
|
|
for _bytes in (True, False): |
186
|
|
|
for _words in (True, False): |
187
|
|
|
for _padding in (0, 1000, 2048): |
188
|
|
|
callback(_bytes, _words, _padding) |
189
|
|
|
|