# Coverage for tests/e2e/fuzz/common.py : 81%

## 229 statements 188 run 41 missing 0 excluded 18 partial

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1# -*- coding: utf-8 -*-

2 from __future__ import annotations

3

4# -- stdlib --

5 from itertools import chain, combinations as C, permutations as P

6 from typing import Any, cast

7 import logging

8 import random

9

10# -- third party --

11 import gevent

12

13# -- own --

14 from game.base import EventHandler

15 from thb.actions import ActionStageLaunchCard, CardChooser, skill_wrap, CharacterChooser

16 from thb.inputlets import ActionInputlet, ChooseOptionInputlet, ChoosePeerCardInputlet

17

18

19# -- code --

20 log = logging.getLogger('UserInputFuzzingHandler')

21

22

23 def let_it_go(*cores):

24 while True:

25 gevent.idle(-100)

26

27 for i in range(10): 27 ↛ 33 line 27 didn't jump to line 33, because the loop on line 27 didn't complete

28 if any(c.server._ep.active for c in cores):

29 break

30

31 gevent.sleep(0.05)

32 else:

33 raise Exception('STUCK!')

34

35 for c in cores:

36 c.server._ep.active = False

37

38

39 class UserInputFuzzingHandler(EventHandler):

40

41 def handle(self, evt: str, arg: Any) -> Any:

42 if evt == 'user_input':

43 trans, ilet = arg

44 self.react(trans, ilet)

45 elif evt in ('action_before', 'action_apply', 'action_after'):

46 self.effect_meta(evt, arg)

47

48 return arg

49

50 def effect_meta(self, evt: str, act: Any):

51 while hasattr(act, 'ui_meta'):

52 _type = {

53 'action_before': 'effect_string_before',

54 'action_apply': 'effect_string_apply',

55 'action_after': 'effect_string',

56 }[evt]

57 prompt = getattr(act.ui_meta, _type, None)

58 if not prompt: return

59 prompt(act)

60 break

61

62 while hasattr(act, 'ui_meta'):

63 if evt == 'action_before':

64 rays = getattr(act.ui_meta, 'ray', None)

65 rays = rays(act) if rays else []

66

67 _type = {

68 'action_before': 'sound_effect_before',

69 'action_apply': 'sound_effect_apply',

70 'action_after': 'sound_effect',

71 }[evt]

72 se = getattr(act.ui_meta, _type, None)

73 se = se and se(act)

74 break

75

76 def react(self, trans, ilet):

77 p = ilet.actor

78 g = self.game

79

80 if trans.name == 'ActionStageAction':

81 if random.random() < 0.05:

82 return False

83

84 cards = list(p.showncards) + list(p.cards) + list(p.equips)

85

86 while random.random() < 0.5:

87 # Skill

88 skl = [sk for sk in p.skills if 't_None' not in sk.target.__name__]

89 [sk.ui_meta.clickable() for sk in skl]

90 if skl:

91 sk = random.choice(skl)

92 else:

93 break

94

95 # Card

96 cc = list(chain(*[C(cards, i) for i in range(4)]))

97 random.shuffle(cc)

98

99 for cl in cc:

100 for tl in self.possible_targets(g, p, sk):

101 c = skill_wrap(p, [sk], cl, {})

102

103 ok, reason = self.ui_meta_walk_wrapped([c])

104 if not ok: 104 ↛ 105 line 104 didn't jump to line 105, because the condition on line 104 was never true

105 assert not c.check(), (c, c.associated_cards, c.check(), ok, reason)

106 continue

107

108 try:

109 tl, ok = c.target(p, tl)

110 except Exception as e:

111 raise Exception(f"{c}.target: {c.target} failed") from e

112

113 try:

114 ok2, reason = c.ui_meta.is_action_valid(c, tl)

115 except Exception as e:

116 raise Exception(f"{c}.ui_meta.is_action_valid failed") from e

117

118 # This can happen

119 # assert (ok and bool(c.check())) == ok2, {

120 # 'ok': ok,

121 # 'c.check()': c.check(),

122 # 'ok2': ok2,

123 # 'c': c,

124 # 'tl': tl,

125 # 'reason': reason,

126 # }

127

128 if not ok:

129 continue

130

131 # This can happen too

132 # assert c.ui_meta.clickable(), c

133

134 if self.try_launch(ilet, cl, tl, skills=[sk]):

135 return

136 break

137

138 for c in cards:

139 if not c.associated_action:

140 continue

141

142 for t in self.possible_targets(g, p, c):

143 if self.try_launch(ilet, [c], t):

144 return True

145

146 elif trans.name in ('Action', 'AskForRejectAction') and isinstance(ilet, ActionInputlet):

147 rst = {

148 'skills': [],

149 'cards': [],

150 'characters': [],

151 }

152

153 if ilet.categories:

154 initiator = cast(CardChooser, ilet.initiator)

155 cond = initiator.cond

156 cl = list(p.showncards) + list(p.cards)

157

158 found = False

159 for skcls in ilet.actor.skills:

160 sk = skcls(ilet.actor)

161 if not sk.ui_meta.clickable():

162 continue

163

164 uiok, uireason = self.ui_meta_walk_wrapped([sk])

165 cct_ok, cct_reason = initiator.ui_meta.choose_card_text(initiator, [sk])

166 if cond([sk]) and random.random() < 0.5:

167 for c in chain([], C(cl, 1), C(cl, 2), [cl]): 167 ↛ 177 line 167 didn't jump to line 177, because the loop on line 167 didn't complete

168 sk = skill_wrap(p, [skcls], c, {})

169 uiok, uireason = self.ui_meta_walk_wrapped([sk])

170 assert uiok

171 assert cond([sk])

172 rst['skills'] = [skcls]

173 rst['cards'] = c

174 found = True

175 break

176

177 if found:

178 break

179 else:

180 for c in chain(C(cl, 1), C(cl, 2), [cl]):

181 cct_ok, cct_reason = initiator.ui_meta.choose_card_text(initiator, c)

182 if cond(c):

183 assert cct_ok, (c, cct_reason)

184 rst['cards'] = c

185 break

186

187 if ilet.candidates:

188 initiator = cast(CharacterChooser, ilet.initiator)

189 target = initiator.choose_player_target

190 # (self, pl: Sequence[Character]) -> Tuple[List[Character], bool]: ...

191 pl = ilet.candidates

192 for p in chain(C(pl, 1), C(pl, 2), [pl]):

193 p, ok = target(p)

194 ccp_ok, ccp_reason = initiator.ui_meta.target(p)

195 if ok:

196 assert ccp_ok, (p, ccp_reason)

197 rst['characters'] = p

198 break

199

200 if rst: 200 ↛ 203 line 200 didn't jump to line 203, because the condition on line 200 was never false

201 ilet.set_result(**rst)

202

203 return True

204

205 elif trans.name == 'ChooseOption' and isinstance(ilet, ChooseOptionInputlet):

206 ilet.set_option(random.choice(list(ilet.options) * 2 + [None]))

207

208 elif trans.name == 'ChoosePeerCard' and isinstance(ilet, ChoosePeerCardInputlet):

209 tgt = ilet.target

210 if random.random() < 0.9:

211 cats = [getattr(tgt, i) for i in ilet.categories]

212 cl = list(chain(*cats))

213 if cl:

214 ilet.set_card(random.choice(cl))

215 elif trans.name == 'SortCharacter':

216 pass

217 elif trans.name == 'ChooseGirl':

218 from settings import TESTING_CHARACTERS as TESTS

219 choices = [c for c in ilet.mapping[ilet.actor] if c.char_cls and c.char_cls.__name__ in TESTS]

220 if choices: 220 ↛ 221 line 220 didn't jump to line 221, because the condition on line 220 was never true

221 c = random.choice(choices)

222 log.info('Got %s', c.char_cls)

223 ilet.set_choice(c)

224 elif trans.name == 'HarvestChoose':

225 pass

226 elif trans.name == 'Pindian':

227 pass

228 elif trans.name == 'HopeMask':

229 pass

230 elif trans.name == 'Prophet':

231 pass

232 elif trans.name == 'ChooseIndividualCard': 232 ↛ 233 line 232 didn't jump to line 233, because the condition on line 232 was never true

233 pass

234 elif trans.name == 'BanGirl':

235 pass

236 elif trans.name == 'GalgameDialog': 236 ↛ 239 line 236 didn't jump to line 239, because the condition on line 236 was never false

237 pass

238 else:

239 log.warning('Not processing %s transaction', trans.name)

240 1/0

241

242 def ui_meta_walk_wrapped(self, cl, check_is_complete=False):

243 from thb.cards.base import Skill

244 for c in cl:

245 if not isinstance(c, Skill):

246 continue

247

248 if check_is_complete: 248 ↛ 249 line 248 didn't jump to line 249, because the condition on line 248 was never true

249 rst, reason = c.ui_meta.is_complete(c)

250 if not rst:

251 return rst, reason

252

253 rst, reason = self.ui_meta_walk_wrapped(c.associated_cards, True)

254 if not rst: 254 ↛ 255 line 254 didn't jump to line 255, because the condition on line 254 was never true

255 return rst, reason

256

257 return True, 'OK'

258

259 def try_launch(self, ilet, cl, tl, skills=[]):

260 p = ilet.actor

261 if skills:

262 c = skill_wrap(p, skills, cl, {})

263 else:

264 assert len(cl) == 1, cl

265 c, = cl

266

267 act = ActionStageLaunchCard(p, tl, c)

268 if act.can_fire():

269 ilet.set_result(skills=skills, cards=cl, characters=tl)

270 return True

271

272 return False

273

274 def possible_targets(self, g, me, c):

275 target = c.target

276 tn = target.__name__

277 pl = g.players

278

279 if tn == 't_None': 279 ↛ 280 line 279 didn't jump to line 280, because the condition on line 279 was never true

280 raise Exception('Fuck')

281

282 elif tn == 't_Self': 282 ↛ 283 line 282 didn't jump to line 283, because the condition on line 282 was never true

283 rl = [[me]]

284

285 elif tn == 't_OtherOne': 285 ↛ 286 line 285 didn't jump to line 286, because the condition on line 285 was never true

286 rl = list(pl)

287 random.shuffle(rl)

288 rl = [[i] for i in rl if i is not me]

289

290 elif tn == 't_One': 290 ↛ 291 line 290 didn't jump to line 291, because the condition on line 290 was never true

291 rl = list(pl)

292 random.shuffle(rl)

293 rl = [[i] for i in rl]

294

295 elif tn == 't_All': 295 ↛ 296 line 295 didn't jump to line 296, because the condition on line 295 was never true

296 rl = list(pl)

297 rl.remove(me)

298 rl = [rl]

299

300 elif tn == 't_AllInclusive': 300 ↛ 301 line 300 didn't jump to line 301, because the condition on line 300 was never true

301 rl = [pl]

302

303 elif tn == '_t_OtherLessEqThanN': 303 ↛ 304 line 303 didn't jump to line 304, because the condition on line 303 was never true

304 n = target._for_test_OtherLessEqThanN

305 rl = []

306 for i in range(n+1):

307 rl.extend(list(P(pl, i)))

308 random.shuffle(rl)

309

310 elif tn == 't_OneOrNone': 310 ↛ 311 line 310 didn't jump to line 311, because the condition on line 310 was never true

311 rl = [[i] for i in pl]

312 rl.append([])

313 random.shuffle(rl)

314

315 elif tn == '_t_OtherN': 315 ↛ 316 line 315 didn't jump to line 316, because the condition on line 315 was never true

316 n = target._for_test_OtherN

317 rl = list(P(pl, n))

318 random.shuffle(rl)

319 rl = [list(i) for i in rl]

320 else:

321 rl = []

322 for i in range(3): # HACK: should be enough

323 rl.extend(list(P(pl, i)))

324 random.shuffle(rl)

325

326 return rl