-
Notifications
You must be signed in to change notification settings - Fork 1
/
tests.py
359 lines (314 loc) · 16 KB
/
tests.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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
import cProfile
import glob
import os
import sys
import random
import shutil
import unittest
from typing import OrderedDict, Union
from hashlib import md5
from mmap import mmap, ACCESS_READ
from pathlib import Path
from typeguard import typechecked, install_import_hook
from unittest import case
profiling = False
typechecks = not profiling
if typechecks:
install_import_hook('BuildLibs')
install_import_hook('BuildGames')
install_import_hook('GUI')
from BuildLibs import buildmapbase, buildmap, crc32, trace, setVerbose, GetVersion, warning
from BuildLibs.grpbase import GrpBase
from BuildLibs.grp import GrpZipFile, LoadGrpFile, RffCrypt, GrpOutput
import BuildGames
from GUI import gui, launcher
unittest.TestLoader.sortTestMethodsUsing = None
temp:Path = Path('temp/')
zippath:Path = Path('duke3d-shareware.grp.zip')
tempgrp:Path = Path('temp/testing.grp')
settings = {
'MapFile.chanceDupeItem': 0.5,
'MapFile.chanceDeleteItem': 0.5,
'MapFile.chanceDupeEnemy': 0.5,
'MapFile.chanceDeleteEnemy': 0.5,
'MapFile.itemVariety': 0,
'MapFile.enemyVariety': 0,
'conFile.range': 1,
'conFile.scale': 1,
'conFile.difficulty': 0.5,
'outputMethod': 'folder',
}
different_settings = {
'MapFile.chanceDupeItem': 0.75,
'MapFile.chanceDeleteItem': 0.25,
'MapFile.chanceDupeEnemy': 0.75,
'MapFile.chanceDeleteEnemy': 0.25,
'MapFile.itemVariety': 0,
'MapFile.enemyVariety': 0,
'conFile.range': 1.5,
'conFile.scale': 0.8,
'conFile.difficulty': 0.7,
'outputMethod': 'folder',
}
# zipped for tests
BuildGames.AddGame('ZIPPED Shareware DUKE3D.GRP v1.3D', 'Duke Nukem 3D', 4570468, 'BFC91225', '9eacbb74e107fa0b136f189217ce41c7', '4bdf21e32ec6a3fc43092a50a51fce3e4ad6600d') # ZIPPED Shareware DUKE3D.GRP v1.3D for tests
# we need the correct file order so we can match the md5
original_order = [
'DEFS.CON', 'GAME.CON', 'USER.CON', 'D3DTIMBR.TMB', 'DUKESW.BIN', 'LOOKUP.DAT', 'PALETTE.DAT',
'TABLES.DAT', 'SECRET.VOC', 'BLANK.VOC', 'ROAM06.VOC', 'ROAM58.VOC', 'PREDRG.VOC', 'GBLASR01.VOC',
'PREDPN.VOC', 'PREDDY.VOC', 'CHOKN12.VOC', 'PREDRM.VOC', 'LIZSPIT.VOC', 'PIGRM.VOC', 'ROAM29.VOC',
'ROAM67.VOC', 'PIGRG.VOC', 'PIGPN.VOC', 'PIGDY.VOC', 'PIGWRN.VOC', 'OCTARM.VOC', 'OCTARG.VOC', 'OCTAAT1.VOC',
'OCTAAT2.VOC', 'OCTAPN.VOC', 'OCTADY.VOC', 'BOS1RM.VOC', 'BOS1RG.VOC', 'BOS1PN.VOC', 'BOS1DY.VOC', 'KICKHIT.VOC',
'RICOCHET.VOC', 'BULITHIT.VOC', 'PISTOL.VOC', 'CLIPOUT.VOC', 'CLIPIN.VOC', 'CHAINGUN.VOC', 'SHOTGNCK.VOC',
'RPGFIRE.VOC', 'BOMBEXPL.VOC', 'PBOMBBNC.VOC', 'WPNSEL21.VOC', 'SHRINK.VOC', 'LSRBMBPT.VOC', 'LSRBMBWN.VOC',
'SHRINKER.VOC', 'VENTBUST.VOC', 'GLASS.VOC', 'GLASHEVY.VOC', 'SHORTED.VOC', 'SPLASH.VOC', 'ALERT.VOC', 'REACTOR.VOC',
'SUBWAY.VOC', 'GEARGRND.VOC', 'GASP.VOC', 'GASPS07.VOC', 'PISSING.VOC', 'KNUCKLE.VOC', 'DRINK18.VOC', 'EXERT.VOC',
'HARTBEAT.VOC', 'PAIN13.VOC', 'PAIN28.VOC', 'PAIN39.VOC', 'PAIN87.VOC', 'WETFEET.VOC', 'LAND02.VOC', 'DUCTWLK.VOC',
'PAIN54.VOC', 'PAIN75.VOC', 'PAIN93.VOC', 'PAIN68.VOC', 'DAMN03.VOC', 'DAMNIT04.VOC', 'COMEON02.VOC', 'WAITIN03.VOC',
'COOL01.VOC', 'AHMUCH03.VOC', 'DANCE01.VOC', 'LETSRK03.VOC', 'READY2A.VOC', 'RIPEM08.VOC', 'ROCKIN02.VOC',
'AHH04.VOC', 'GULP01.VOC', 'PAY02.VOC', 'AMESS06.VOC', 'BITCHN04.VOC', 'DOOMED16.VOC', 'HOLYCW01.VOC',
'HOLYSH02.VOC', 'IMGOOD12.VOC', 'ONLYON03.VOC', 'PIECE02.VOC', 'RIDES09.VOC', '2RIDE06.VOC', 'THSUK13A.VOC',
'WANSOM4A.VOC', 'MYSELF3A.VOC', 'NEEDED03.VOC', 'SHAKE2A.VOC', 'DUKNUK14.VOC', 'GETSOM1A.VOC', 'GOTHRT01.VOC',
'GROOVY02.VOC', 'WHRSIT05.VOC', 'BOOBY04.VOC', 'DIESOB03.VOC', 'DSCREM04.VOC', 'LOOKIN01.VOC', 'PISSIN01.VOC',
'GETITM19.VOC', 'SCUBA.VOC', 'JETPAKON.VOC', 'JETPAKI.VOC', 'JETPAKOF.VOC', 'GOGGLE12.VOC', 'THUD.VOC',
'SQUISH.VOC', 'TELEPORT.VOC', 'GBELEV01.VOC', 'GBELEV02.VOC', 'SWITCH.VOC', 'FLUSH.VOC', 'QUAKE.VOC',
'MONITOR.VOC', 'POOLBAL1.VOC', 'ONBOARD.VOC', 'BUBBLAMB.VOC', 'MACHAMB.VOC', 'WIND54.VOC', 'STEAMHIS.VOC',
'BARMUSIC.VOC', 'WARAMB13.VOC', 'WARAMB21.VOC', 'WARAMB23.VOC', 'WARAMB29.VOC', 'COMPAMB.VOC', 'SLIDOOR.VOC',
'OPENDOOR.VOC', 'EDOOR10.VOC', 'EDOOR11.VOC', 'FSCRM10.VOC', 'H2OGRGL2.VOC', 'GRIND.VOC', 'ENGHUM.VOC',
'LAVA06.VOC', 'PHONBUSY.VOC', 'ROAM22.VOC', 'AMB81B.VOC', 'ROAM98B.VOC', 'H2ORUSH2.VOC', 'PROJRUN.VOC',
'FIRE09.VOC', '!PRISON.VOC', '!PIG.VOC', '!BOSS.VOC', 'MICE3.VOC', 'DRIP3.VOC', 'ITEM15.VOC', 'BONUS.VOC',
'CATFIRE.VOC', 'KILLME.VOC', 'SHOTGUN7.VOC', 'DMDEATH.VOC', 'HAPPEN01.VOC', 'DSCREM15.VOC', 'DSCREM16.VOC',
'DSCREM17.VOC', 'DSCREM18.VOC', 'DSCREM38.VOC', 'RIP01.VOC', 'NOBODY01.VOC', 'CHEW05.VOC', 'LETGOD01.VOC',
'HAIL01.VOC', 'BLOWIT01.VOC', 'EATSHT01.VOC', 'FACE01.VOC', 'INHELL01.VOC', 'SUKIT01.VOC', 'PISSES01.VOC',
'GRABBAG.MID', 'STALKER.MID', 'DETHTOLL.MID', 'STREETS.MID', 'WATRWLD1.MID', 'SNAKE1.MID', 'THECALL.MID',
'TILES000.ART', 'TILES001.ART', 'TILES002.ART', 'TILES003.ART', 'TILES004.ART', 'TILES005.ART', 'TILES006.ART',
'TILES007.ART', 'TILES008.ART', 'TILES009.ART', 'TILES010.ART', 'TILES011.ART', 'TILES012.ART', 'E1L1.MAP',
'E1L2.MAP', 'E1L3.MAP', 'E1L4.MAP', 'E1L5.MAP', 'E1L6.MAP'
]
@typechecked
class BERandoTestCase(unittest.TestCase):
def subTest(self, msg=case._subtest_msg_sentinel, **params):
print('\n----------------------------------\nstarting subTest', msg, '\n----------------------------------')
return super().subTest(msg, **params)
@classmethod
def tearDownClass(cls):
# GitHub Actions doesn't show STDERR
print('Finished '+ str(cls.__name__))
super().tearDownClass()
def test_get_version(self):
print(GetVersion())
print('Python version:', sys.version_info)
d = OrderedDict(zero=0, one=1, two=2, three=3, four=4, five=5, six=6, seven=7, eight=8, nine=9, ten=10)
l = list(d.values())
self.assertListEqual(l, list(range(0, 11)), 'OrderedDict kargs init ordering')
d = OrderedDict({'zero':0, 'one':1, 'two':2, 'three':3, 'four':4, 'five':5, 'six':6, 'seven':7, 'eight':8, 'nine':9, 'ten':10})
l = list(d.values())
self.assertListEqual(l, list(range(0, 11)), 'OrderedDict dict init ordering')
def test_1_extract_zipgrp(self):
# I zipped the GRP file to save space in the repo
# but also Ion Fury uses ZIP format anyways so we do need to test it
grp: GrpBase|None = None
with self.subTest('ExtractAll'):
grp = LoadGrpFile(zippath)
self.assertEqual(len(grp.files), len(original_order))
grp.ExtractAll(temp)
with self.subTest('CreateGrpFile'):
CreateGrpFile(temp, tempgrp, original_order)
grp = None
with self.subTest('Verify new GRP File'):
grp = LoadGrpFile(tempgrp)
self.assertEqual(grp.game.type, 'Duke Nukem 3D')
def test_rng(self):
# ensure the rng is OS indepedent
rng = random.Random('0451')
self.assertEqual(rng.randint(1, 10), 8)
self.assertEqual(rng.choice(range(10)), 2)
self.assertEqual(int(rng.random()*1000), 433)
def test_rando(self):
# first get vanilla MD5s
with self.subTest('Open GRP File'):
grp: GrpBase = LoadGrpFile(tempgrp)
vanilla = self.Md5GameFiles('Vanilla', grp, temp)
self.assertIsNotNone(vanilla, 'Vanilla MD5s')
self.assertGreater(len(vanilla), 0, 'Vanilla MD5s')
# now test randomizing with different seeds and settings, comparing MD5s each time
grp0451 = self.TestRandomize(tempgrp, 451, vanilla, False)
print(repr(grp0451))
# update these when making behavioral changes, but otherwise they should stay the same
self.assertDictEqual(
{
'E1L1.MAP': '92d3555d92495f1d158844def24d3653',
'E1L2.MAP': 'f58855a55af234be8d1de4f145dc07fa',
'E1L3.MAP': '9fb5450bd3781d70fa3e95d70aa50b1a',
'E1L4.MAP': 'f3fd61103fe07e90afbf19f2b99a83fb',
'E1L5.MAP': '8affa9fe8ddb2ecc5951a87788629417',
'E1L6.MAP': 'c8d921798945faa9a9321ad018fc80d2',
'USER.CON': '24e6c7ce98064e633528d92380002d55'
},
grp0451
)
self.TestRandomize(zippath, 2052, grp0451, False)
self.TestRandomize(tempgrp, 451, grp0451, True)
self.TestRandomize(tempgrp, 451, grp0451, False, settings=different_settings)
def test_grp_output(self):
settings_grp = settings.copy()
settings_grp['outputMethod'] = 'grp'
grp:GrpBase = LoadGrpFile(tempgrp)
grp.Randomize(451, settings=settings_grp, basepath=Path(tempgrp.parent, 'test_grp_output'))
def test_external_files(self):
try:
with self.subTest('Create External File'):
testdata = 'Damn, I\'m lookin good!'
extname = 'external_file.txt'
gamedir = Path(tempgrp).parent
extpath = Path(gamedir, extname)
with open(extpath, 'w') as file:
file.write(testdata)
with self.subTest('Open GRP File'):
BuildGames.AddGame('Shareware DUKE3D.GRP v1.3D', 'Duke Nukem 3D', 11035779, '983AD923', 'C03558E3A78D1C5356DC69B6134C5B55', 'A58BDBFAF28416528A0D9A4452F896F46774A806', externalFiles=True, allowOverwrite=True) # Shareware DUKE3D.GRP v1.3D
grp: GrpBase = LoadGrpFile(tempgrp)
with self.subTest('Read External File'):
t = grp.getfile(extname).decode()
self.assertEqual(t, testdata, 'Got external file path override')
finally:
BuildGames.AddGame('Shareware DUKE3D.GRP v1.3D', 'Duke Nukem 3D', 11035779, '983AD923', 'C03558E3A78D1C5356DC69B6134C5B55', 'A58BDBFAF28416528A0D9A4452F896F46774A806', externalFiles=False, allowOverwrite=True) # Shareware DUKE3D.GRP v1.3D
def test_game_settings(self):
for game in BuildGames.gamesList.values():
self.CheckGame(game)
def CheckGame(self, game: BuildGames.GameInfo):
try:
mapSettings: BuildGames.GameMapSettings = BuildGames.GetGameMapSettings(game)
except Exception as e:
warning(e)
return
for e in mapSettings.addableEnemies:
self.assertIn(e, mapSettings.swappableEnemies)
for map in mapSettings.additions.values():
for addition in map:
for sprite in addition['choices']:
self.assertIn(sprite, mapSettings.swappableItems)
#@unittest.skip
def test_other_grps(self):
# optionally use the othertests folder for testing your own collection of games that aren't freeware
files = glob.glob('othertests/*', recursive=True)
for f in files:
if 'skip' in f:
continue
with self.subTest(f):
f = Path(f)
self.TestRandomize(f, 451, {}, False)
#grp = LoadGrpFile(f)
#grp.ExtractAll(Path(temp,str(f)+'-extracted'))
def test_combine(self):
# optionally make a new grp file
globfiles = glob.glob('combine/*', recursive=True)
files = []
for f in globfiles:
f = Path(f)
if f.is_file():
files.append(f)
if files:
CreateGrpFile(Path(''), Path('temp/combined.grp'), files)
def TestRandomize(self, grppath:Path, seed:int, oldMd5s:dict, shouldMatch:bool, settings:dict=settings) -> dict:
self.maxDiff = None
testname = str(grppath) + ' Seed ' + ('0451' if seed==451 else str(seed))
foldername = grppath.name + '-' + str(crc32(testname+repr(settings)))
basepath = Path(temp, foldername)
newMd5s = None
with self.subTest('Randomize '+testname):
grp:GrpBase = LoadGrpFile(grppath)
grp.Randomize(seed, settings=settings, basepath=basepath)
gametype = grp.game.type
if settings['outputMethod'] == 'folder':
basepath = Path(basepath, 'Randomizer')
newMd5s = self.Md5GameFiles(testname, grp, basepath)
self.assertIsNotNone(newMd5s, 'New MD5s')
self.assertGreater(len(newMd5s), 0, 'New MD5s')
if oldMd5s:
self.assertIsNotNone(oldMd5s, 'Old MD5s')
self.assertGreater(len(oldMd5s), 0, 'Old MD5s')
if shouldMatch:
self.assertDictEqual(oldMd5s, newMd5s)
else:
self.assertEqual(len(oldMd5s), len(newMd5s), 'Same number of files')
for k in oldMd5s.keys():
self.assertNotEqual(oldMd5s[k], newMd5s[k], k)
with open(Path(basepath, gametype + ' Randomizer.html')) as spoilerlog:
logs = spoilerlog.read()
self.assertGreater(len(logs), 10, 'found spoiler logs')
self.assertInLogs('Randomizing with seed: '+str(seed), logs)
for con in grp.gameSettings.conFiles.keys():
con = str(Path(con))
self.assertInLogs('Finished randomizing file: '+con, logs)
for map in grp.GetAllFilesEndsWith('.map'):
map = str(Path(map))
self.assertInLogs('Finished randomizing file: '+map, logs)
self.assertInLogs('<div class="ListSprites">', logs)
if grp.mapSettings.triggers:
self.assertInLogs('set hightag to ', logs)
self.assertInLogs('<div class="FileSection">', logs)
return newMd5s
def assertInLogs(self, text, logs):
if text.lower() not in logs.lower():
self.fail(text + ' not found in logs')
def Md5GameFiles(self, testname:str, grp:GrpBase, basepath:Path) -> dict:
with self.subTest('MD5 '+testname):
maps = grp.GetAllFilesEndsWith('.map')
cons = list(grp.gameSettings.conFiles.keys())
return self.Md5Files(basepath, (maps+cons))
def Md5Files(self, basepath: Path, filenames: list) -> dict:
md5s = {}
for f in filenames:
t = self.Md5File(Path(basepath, f))
md5s[f] = t
trace('Md5Files ', basepath, ': ', md5s)
return md5s
def Md5File(self, filename:Path):
with open(filename) as file, mmap(file.fileno(), 0, access=ACCESS_READ) as file:
md5sum = md5(file).hexdigest()
return md5sum
def test_walls(self):
with self.subTest('test PointIsInShape'):
walls = [(0,0), (10,0), (10,10), (0,10)]
self.assertEqual(buildmapbase.PointIsInShape(walls, (5,5), 0) % 2, 1)
self.assertEqual(buildmapbase.PointIsInShape(walls, (15,15), 0) % 2, 0)
def test_blood_crypt(self):
# ensure it's reversible
data = bytearray(b'123456')
d2 = buildmap.MapCrypt(data, 451)
d2 = buildmap.MapCrypt(d2, 451)
d2 = RffCrypt(d2, 451)
d2 = RffCrypt(d2, 451)
self.assertEqual(data, d2)
data = bytearray(b'dfgdfghcvbngertguhyrtujityr3456436fdghsdfgxcvb')
d2 = buildmap.MapCrypt(data, 0x7474614d)
d2 = buildmap.MapCrypt(d2, 0x7474614d)
d2 = RffCrypt(d2, 451)
d2 = RffCrypt(d2, 451)
self.assertEqual(data, d2)
def test_mirror(self):
orig = [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] # verticies are counter-clockwise, right?
desired = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
result = buildmapbase.MirrorList(orig)
self.assertListEqual(result, desired, 'mirror')
def CreateGrpFile(frompath: Path, outpath: Path, filenames: list) -> None:
with GrpOutput(outpath, 'test', len(filenames), '', 'GAME.CON', None, 0, 0) as out:
for name in filenames:
with open(Path(frompath, name), 'rb') as f:
d = f.read()
out.write(str(name), d)
def runtests():
unittest.main(verbosity=9, warnings="error", failfast=True)
if __name__ == "__main__":
try:
if os.path.isdir(temp):
shutil.rmtree(temp)
if profiling:
setVerbose(-10)
cProfile.run("runtests()", sort="cumtime")
else:
runtests()
finally:
# if os.path.isdir(temp):
# shutil.rmtree(temp)
pass