Coverage for CIResults/tests/test_filtering.py: 100%

565 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-19 09:20 +0000

1import datetime 

2from unittest.mock import patch 

3 

4import pytz 

5from django.core.management import call_command 

6from django.db import models 

7from django.db.models import Q 

8from django.test import TestCase 

9from django.test.client import RequestFactory 

10from model_bakery import baker 

11 

12from CIResults.filtering import ( 

13 FilterObject, 

14 FilterObjectBool, 

15 FilterObjectDateTime, 

16 FilterObjectDuration, 

17 FilterObjectInteger, 

18 FilterObjectJSON, 

19 FilterObjectModel, 

20 FilterObjectStr, 

21 LegacyParser, 

22 QueryCreator, 

23 QueryParser, 

24 QueryParserPython, 

25 UserFiltrableMixin, 

26 VisitorQ, 

27) 

28from CIResults.models import ( 

29 Bug, 

30 Issue, 

31 KnownFailure, 

32 Machine, 

33 MachineTag, 

34 RunConfigTag, 

35 TestResult, 

36 TestSuite, 

37 TestsuiteRun, 

38) 

39from shortener.models import Shortener 

40 

41 

42class UserFiltrableTestsMixin: 

43 def test_filter_objects_to_db(self): 

44 # Abort if the class does not have a model 

45 if not hasattr(self, 'Model'): 

46 raise ValueError("The class '{}' does not have a 'Model' attribute".format(self)) # pragma: no cover 

47 

48 # Abort if the object does not have the filter_objects_to_db attribute 

49 if not hasattr(self.Model, 'filter_objects_to_db'): 

50 raise ValueError("The model '{}' does not have a 'filter_objects_to_db " 

51 "attribute'".format(self.Model)) # pragma: no cover 

52 

53 # execute the query with USE_TZ=False to ignore the naive datetime warning 

54 with self.settings(USE_TZ=False): 

55 for field_name, db_obj in self.Model.filter_objects_to_db.items(): 

56 if isinstance(db_obj, FilterObjectModel): 

57 filter_name = '{}__in'.format(db_obj.db_path) 

58 value = db_obj.model.objects.none() 

59 elif isinstance(db_obj, FilterObjectJSON): 

60 db_obj.key = 'key' 

61 filter_name = '{}__exact'.format(db_obj.db_path) 

62 value = db_obj.test_value 

63 else: 

64 filter_name = '{}__exact'.format(db_obj.db_path) 

65 value = db_obj.test_value 

66 

67 try: 

68 self.Model.objects.filter(**{filter_name: value}) 

69 except Exception as e: # pragma: no cover 

70 self.fail("Class {}'s field '{}' is not working: {}.".format(self.Model, 

71 field_name, 

72 str(e))) # pragma: no cover 

73 

74 

75class BugTests(TestCase, UserFiltrableTestsMixin): 

76 Model = Bug 

77 

78 

79class IssueTests(TestCase, UserFiltrableTestsMixin): 

80 Model = Issue 

81 

82 

83class TestsuiteRunTests(TestCase, UserFiltrableTestsMixin): 

84 Model = TestsuiteRun 

85 

86 

87class TestResultTests(TestCase, UserFiltrableTestsMixin): 

88 Model = TestResult 

89 

90 

91class KnownFailureTests(TestCase, UserFiltrableTestsMixin): 

92 Model = KnownFailure 

93 

94 

95class QueryVisitorTests(TestCase): 

96 def test_get_related_model(self): 

97 queryVisitor = VisitorQ(KnownFailure) 

98 self.assertEqual(queryVisitor.get_related_model("result"), TestResult) 

99 

100 def test_get_related_model_no_attribute(self): 

101 queryVisitor = VisitorQ(KnownFailure) 

102 with self.assertRaisesMessage(AttributeError, "'KnownFailure' has no attribute 'status'"): 

103 queryVisitor.get_related_model("status") 

104 

105 

106class TestModelChild(models.Model): 

107 number = models.IntegerField() 

108 string = models.CharField(max_length=100) 

109 filter_objects_to_db = { 

110 "number": FilterObjectInteger('number'), 

111 "string": FilterObjectStr('string'), 

112 } 

113 

114 

115class TestModel(models.Model): 

116 number = models.IntegerField() 

117 string = models.CharField(max_length=100) 

118 date = models.DateTimeField() 

119 duration = models.DurationField() 

120 boolean = models.BooleanField() 

121 json = models.JSONField() 

122 child = models.ForeignKey(TestModelChild, on_delete=models.CASCADE) 

123 

124 filter_objects_to_db = { 

125 "number": FilterObjectInteger('number'), 

126 "string": FilterObjectStr('string'), 

127 "date": FilterObjectDateTime('date'), 

128 "duration": FilterObjectDuration('duration'), 

129 "boolean": FilterObjectBool('boolean'), 

130 "json": FilterObjectJSON('json'), 

131 "child": FilterObjectModel(TestModelChild, 'child'), 

132 } 

133 

134 

135call_command('makemigrations', 'CIResults') 

136 

137 

138class QueryParserTests(TestCase): 

139 def test_empty_query(self): 

140 parser = QueryParser(TestResult, "") 

141 self.assertTrue(parser.is_valid, parser.error) 

142 self.assertTrue(parser.is_empty) 

143 self.assertEqual(parser.q_objects, Q()) 

144 self.assertEqual(parser.error, None) 

145 

146 def test_unknown_object_name(self): 

147 parser = QueryParser(TestResult, "hello = 'world'") 

148 

149 self.assertFalse(parser.is_valid, parser.error) 

150 self.assertTrue(parser.is_empty) 

151 self.assertEqual(parser.q_objects, Q()) 

152 self.assertEqual(parser.error, "The object 'hello' does not exist") 

153 

154 def test_key_with_double_underscore(self): 

155 parser = QueryParser(TestResult, "json.toto__tata = 'world'") 

156 

157 self.assertFalse(parser.is_valid, parser.error) 

158 self.assertTrue(parser.is_empty) 

159 self.assertEqual(parser.q_objects, Q()) 

160 self.assertEqual(parser.error, "Dict object keys cannot contain the substring '__'") 

161 

162 def test_two_keys_on_keyed_object(self): 

163 parser = QueryParser(TestModel, "json.toto.tata = 'world'") 

164 

165 self.assertFalse(parser.is_valid, parser.error) 

166 self.assertTrue(parser.is_empty) 

167 self.assertEqual(parser.q_objects, Q()) 

168 

169 def test_no_key_on_keyed_object(self): 

170 parser = QueryParser(TestModel, "json = 'world'") 

171 

172 self.assertFalse(parser.is_valid, parser.error) 

173 self.assertTrue(parser.is_empty) 

174 self.assertEqual(parser.q_objects, Q()) 

175 self.assertEqual(parser.error, "The dict object 'json' requires a key to access its data") 

176 

177 def test_key_on_non_keyed_object(self): 

178 parser = QueryParser(TestModel, "date.toto = 'world'") 

179 

180 self.assertFalse(parser.is_valid, parser.error) 

181 self.assertTrue(parser.is_empty) 

182 self.assertEqual(parser.q_objects, Q()) 

183 self.assertEqual(parser.error, "The object 'date' cannot have an associated key") 

184 

185 def test_invalid_syntax(self): 

186 parser = QueryParser(TestModel, "hello = 'world") 

187 

188 self.assertFalse(parser.is_valid, parser.error) 

189 self.assertTrue(parser.is_empty) 

190 self.assertEqual(parser.q_objects, Q()) 

191 self.assertEqual(parser.error, "Expected ''' at position (1, 15) => 'o = 'world*'.") 

192 

193 @patch('django.utils.timezone.now', 

194 return_value=datetime.datetime.strptime('2019-01-01T00:00:05', "%Y-%m-%dT%H:%M:%S")) 

195 def test_parsing_all_types(self, now_mocked): 

196 parser = QueryParser(TestModel, "number=123 AND string = 'HELLO' AND date=datetime(2019-02-01) " 

197 "AND duration = duration(00:00:03) AND duration > ago(00:00:05) " 

198 "AND boolean = TRUE AND json.foo_bar = 'bar'" 

199 "AND json.foo_bar2 = 42") 

200 

201 self.assertTrue(parser.is_valid, parser.error) 

202 self.assertFalse(parser.is_empty) 

203 # HACK: Using 'children' attribute and set() here because the ordering was different, for some reason, between 

204 # Q objects, which caused direct comparison to fail. 

205 self.assertEqual( 

206 set(parser.q_objects.children), 

207 set( 

208 Q( 

209 number__exact=123, 

210 string__exact="HELLO", 

211 boolean__exact=True, 

212 date__exact=FilterObjectDateTime.parse_value("2019-02-01"), 

213 duration__exact=datetime.timedelta(seconds=3), 

214 duration__gt=datetime.datetime.strptime("2019-01-01", "%Y-%m-%d"), 

215 json__foo_bar__exact="bar", 

216 json__foo_bar2__exact=42, 

217 ).children 

218 ), 

219 ) 

220 

221 def test_integer_lookups(self): 

222 for lookup, suffix in [("<=", "lte"), (">=", "gte"), ("<", "lt"), (">", "gt"), ("<", "lt"), ("=", "exact")]: 

223 parser = QueryParser(TestModel, f"number {lookup} 1234") 

224 key = f"number__{suffix}" 

225 self.assertEqual(parser.q_objects, Q(**{key: 1234})) 

226 

227 parser = QueryParser(TestModel, "number IS IN [12, 34]") 

228 

229 self.assertTrue(parser.is_valid, parser.error) 

230 self.assertFalse(parser.is_empty) 

231 self.assertEqual(parser.q_objects, Q(number__in=[12, 34])) 

232 

233 def test_string_lookups(self): 

234 for lookup, suffix, negated in [ 

235 ("CONTAINS", "contains", False), 

236 ("ICONTAINS", "icontains", False), 

237 ("MATCHES", "regex", False), 

238 ("~=", "regex", False), 

239 ("=", "exact", False), 

240 ("!=", "exact", True), 

241 ]: 

242 parser = QueryParser(TestModel, f"string {lookup} 'hello'") 

243 

244 key = f"string__{suffix}" 

245 expected = Q(**{key: "hello"}) 

246 

247 if negated: 

248 self.assertEqual(parser.q_objects, ~expected) 

249 else: 

250 self.assertEqual(parser.q_objects, expected) 

251 

252 parser = QueryParser(TestModel, "string IS IN ['hello','world']") 

253 self.assertEqual(parser.q_objects, Q(string__in=['hello', 'world'])) 

254 

255 def test_empty_string_query(self): 

256 parser = QueryParser(TestModel, "string = ''") 

257 key = "string__exact" 

258 expected = Q(**{key: ""}) 

259 self.assertEqual(parser.q_objects, expected) 

260 

261 def test_escaped_string_query(self): 

262 for quote in ["'", '"']: 

263 query = f"string = {quote}foo\\{quote}bar{quote}" 

264 parser = QueryParser(TestModel, query) 

265 self.assertTrue(parser.is_valid) 

266 self.assertEqual(parser.error, None) 

267 expected = Q(**{"string__exact": f"foo\\{quote}bar"}) 

268 self.assertEqual(parser.q_objects, expected) 

269 

270 def test_limit_alone(self): 

271 parser = QueryParser(TestModel, "number=123 AND string = 'HELLO' LIMIT 42") 

272 self.assertEqual(parser.limit, 42) 

273 

274 def test_limit_negative(self): 

275 parser = QueryParser(TestModel, "number=123 AND string = 'HELLO' LIMIT -42") 

276 

277 self.assertFalse(parser.is_valid, ) 

278 self.assertEqual(parser.error, "Negative limits are not supported") 

279 

280 def test_orderby_alone(self): 

281 parser = QueryParser(TestModel, "number=123 AND string = 'HELLO' ORDER_BY -string") 

282 self.assertEqual(parser.orderby, "-string") 

283 

284 def test_orderby_invalid_object(self): 

285 parser = QueryParser(TestModel, "number=123 AND string = 'HELLO' ORDER_BY toto") 

286 

287 self.assertFalse(parser.is_valid, ) 

288 self.assertEqual(parser.error, "The object 'toto' does not exist") 

289 

290 def test_orderby_limit_interaction(self): 

291 parser = QueryParser(TestModel, "number=123 AND string = 'HELLO' ORDER_BY string LIMIT 42") 

292 

293 self.assertTrue(parser.is_valid, parser.error) 

294 self.assertFalse(parser.is_empty) 

295 self.assertEqual(parser.q_objects, 

296 Q(number__exact=123, string__exact='HELLO')) 

297 self.assertEqual(parser.limit, 42) 

298 self.assertEqual(parser.orderby, "string") 

299 

300 def test_invalid_subquery(self): 

301 parser = QueryParser(TestModel, "number=123 AND child MATCHES (not_existient_field=123) AND string = 'TOTO'") 

302 

303 self.assertFalse(parser.is_valid) 

304 self.assertEqual(parser.error, "The object 'not_existient_field' does not exist") 

305 self.assertTrue(parser.is_empty) 

306 

307 def test_subquery(self): 

308 test_result = baker.make(TestResult, ts_run__runconfig__name="run_1") 

309 parser = QueryParser(TestResult, "runconfig MATCHES (name='run_1')") 

310 

311 self.assertTrue(parser.is_valid, parser.error) 

312 self.assertFalse(parser.is_empty) 

313 

314 self.assertIn(test_result, parser.objects) 

315 

316 def test_complex_query1(self): 

317 parser = QueryParser(TestModel, '''(string IS IN ["toto","titi"] AND date=datetime(2018-06-23)) OR 

318 ((number > 456 AND NOT string ~= "hello" ) OR number < 456)''') 

319 q_filter = (Q(**{'string__in': ['toto', 'titi']}) & Q(**{'date__exact': 

320 datetime.datetime(2018, 6, 23, 0, 0, tzinfo=pytz.utc)})) | ((Q(**{'number__gt': 456}) 

321 & ~Q(**{'string__regex': 'hello'})) | 

322 Q(**{'number__lt': 456})) 

323 self.assertTrue(parser.is_valid, parser.error) 

324 self.assertFalse(parser.is_empty) 

325 self.assertEqual(parser.q_objects, q_filter) 

326 

327 def test_complex_query2(self): 

328 parser = QueryParser(TestModel, '''(number IS IN [2,3,4] OR number NOT IN [2,3] ) 

329 AND (number <= 1 OR number >= 0)''') 

330 q_filter = (Q(**{'number__in': [2, 3, 4]}) | ~Q(**{'number__in': [2, 3]}))\ 

331 & (Q(**{'number__lte': 1}) | Q(**{'number__gte': 0})) 

332 

333 self.assertTrue(parser.is_valid, parser.error) 

334 self.assertFalse(parser.is_empty) 

335 self.assertEqual(parser.q_objects, q_filter) 

336 

337 def test_ignore_fields__all_fields_ignored(self): 

338 parser = QueryParser(TestResult, "status_name = 'fail'", ignore_fields=["status_name"]) 

339 self.assertEqual( 

340 ('SELECT DISTINCT "CIResults_testresult"."id", "CIResults_testresult"."test_id", ' 

341 '"CIResults_testresult"."ts_run_id", "CIResults_testresult"."status_id", "CIResults_testresult"."url", ' 

342 '"CIResults_testresult"."start", "CIResults_testresult"."duration", "CIResults_testresult"."command", ' 

343 '"CIResults_testresult"."stdout", "CIResults_testresult"."stderr", "CIResults_testresult"."dmesg" FROM ' 

344 '"CIResults_testresult"'), 

345 str(parser.objects.query) 

346 ) 

347 

348 def test_ignore_fields__complex_query_with_multiple_ignored_fields(self): 

349 parser = QueryParser(TestResult, "status_name = 'fail' AND (stdout = 'out' OR stderr = 'err')", 

350 ignore_fields=["status_name", "stderr"]) 

351 self.assertEqual( 

352 ('SELECT DISTINCT "CIResults_testresult"."id", "CIResults_testresult"."test_id", ' 

353 '"CIResults_testresult"."ts_run_id", "CIResults_testresult"."status_id", "CIResults_testresult"."url", ' 

354 '"CIResults_testresult"."start", "CIResults_testresult"."duration", "CIResults_testresult"."command", ' 

355 '"CIResults_testresult"."stdout", "CIResults_testresult"."stderr", "CIResults_testresult"."dmesg" FROM ' 

356 '"CIResults_testresult" WHERE "CIResults_testresult"."stdout" = out'), 

357 str(parser.objects.query) 

358 ) 

359 

360 def test_equal_m2m_multiple(self): 

361 machine = baker.make(Machine, tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")]) 

362 test_result = baker.make(TestResult, ts_run__machine=machine) 

363 parser = QueryParser(TestResult, "machine_tag = 'tag1' AND machine_tag = 'tag2'") 

364 self.assertTrue(parser.is_valid) 

365 self.assertIn(test_result, parser.objects) 

366 self.assertNotIn(test_result, QueryParser(TestResult, "machine_tag = 'tag1' AND machine_tag = 'tag3'").objects) 

367 

368 

369class PythonQueryParserTests(TestCase): 

370 def test_empty_query(self): 

371 test_result = baker.make(TestResult) 

372 self.assertTrue(QueryParserPython(TestResult, "").matching_fn(test_result)) 

373 

374 def test_invalid_query(self): 

375 self.assertFalse(QueryParserPython(TestResult, "invalid query").is_valid) 

376 self.assertFalse(QueryParserPython(TestResult, "status_name").is_valid) 

377 self.assertFalse(QueryParserPython(TestResult, "status_name = 'fail' AND").is_valid) 

378 

379 def test_equal_query(self): 

380 self.assertTrue( 

381 QueryParserPython(TestResult, "status_name = 'pass'").matching_fn( 

382 baker.make(TestResult, status__name="pass") 

383 ) 

384 ) 

385 self.assertFalse( 

386 QueryParserPython(TestResult, "status_name = 'fail'").matching_fn( 

387 baker.make(TestResult, status__name="pass") 

388 ) 

389 ) 

390 

391 def test_equal_m2m(self): 

392 test_result = baker.make( 

393 TestResult, ts_run__machine__tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")] 

394 ) 

395 self.assertTrue(QueryParserPython(TestResult, "machine_tag = 'tag1'").matching_fn(test_result)) 

396 self.assertTrue(QueryParserPython(TestResult, "machine_tag = 'tag2'").matching_fn(test_result)) 

397 self.assertFalse(QueryParserPython(TestResult, "machine_tag = 'tag3'").matching_fn(test_result)) 

398 

399 def test_equal_m2m_multiple(self): 

400 test_result = baker.make( 

401 TestResult, ts_run__machine__tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")] 

402 ) 

403 self.assertTrue( 

404 QueryParserPython(TestResult, "machine_tag = 'tag1' AND machine_tag = 'tag2'").matching_fn(test_result) 

405 ) 

406 

407 def test_not_equal_query(self): 

408 self.assertTrue( 

409 QueryParserPython(TestResult, "status_name != 'fail'").matching_fn( 

410 baker.make(TestResult, status__name="pass") 

411 ) 

412 ) 

413 self.assertFalse( 

414 QueryParserPython(TestResult, "status_name != 'pass'").matching_fn( 

415 baker.make(TestResult, status__name="pass") 

416 ) 

417 ) 

418 

419 def test_not_prefix(self): 

420 self.assertTrue( 

421 QueryParserPython(TestResult, "NOT status_name = 'fail'").matching_fn( 

422 baker.make(TestResult, status__name="pass") 

423 ) 

424 ) 

425 self.assertFalse( 

426 QueryParserPython(TestResult, "NOT status_name = 'pass'").matching_fn( 

427 baker.make(TestResult, status__name="pass") 

428 ) 

429 ) 

430 

431 def test_contains(self): 

432 self.assertTrue( 

433 QueryParserPython(TestResult, "status_name CONTAINS 'ss'").matching_fn( 

434 baker.make(TestResult, status__name="pass") 

435 ) 

436 ) 

437 self.assertFalse( 

438 QueryParserPython(TestResult, "status_name CONTAINS 'xx'").matching_fn( 

439 baker.make(TestResult, status__name="pass") 

440 ) 

441 ) 

442 

443 def test_icontains(self): 

444 self.assertTrue( 

445 QueryParserPython(TestResult, "status_name ICONTAINS 'SS'").matching_fn( 

446 baker.make(TestResult, status__name="pass") 

447 ) 

448 ) 

449 

450 def test_is_in(self): 

451 self.assertTrue( 

452 QueryParserPython(TestResult, "status_name IS IN ['pass', 'fail']").matching_fn( 

453 baker.make(TestResult, status__name="pass") 

454 ) 

455 ) 

456 self.assertFalse( 

457 QueryParserPython(TestResult, "status_name IS IN ['fail']").matching_fn( 

458 baker.make(TestResult, status__name="pass") 

459 ) 

460 ) 

461 

462 def test_not_in(self): 

463 self.assertTrue( 

464 QueryParserPython(TestResult, "status_name NOT IN ['fail', 'abort']").matching_fn( 

465 baker.make(TestResult, status__name="pass") 

466 ) 

467 ) 

468 self.assertFalse( 

469 QueryParserPython(TestResult, "status_name NOT IN ['fail', 'pass']").matching_fn( 

470 baker.make(TestResult, status__name="pass") 

471 ) 

472 ) 

473 

474 def test_is_in_m2m(self): 

475 test_result = baker.make( 

476 TestResult, ts_run__machine__tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")] 

477 ) 

478 self.assertTrue(QueryParserPython(TestResult, "machine_tag IS IN ['tag1']").matching_fn(test_result)) 

479 self.assertTrue( 

480 QueryParserPython(TestResult, "machine_tag IS IN ['tag1', 'tag2', 'tag3']").matching_fn(test_result) 

481 ) 

482 self.assertFalse(QueryParserPython(TestResult, "machine_tag IS IN ['tag3']").matching_fn(test_result)) 

483 

484 def test_contains_m2m(self): 

485 test_result = baker.make( 

486 TestResult, ts_run__machine__tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")] 

487 ) 

488 self.assertTrue(QueryParserPython(TestResult, "machine_tag CONTAINS ['tag1']").matching_fn(test_result)) 

489 self.assertTrue(QueryParserPython(TestResult, "machine_tag CONTAINS ['tag1', 'tag2']").matching_fn(test_result)) 

490 self.assertFalse( 

491 QueryParserPython(TestResult, "machine_tag CONTAINS ['tag1', 'tag2', 'tag3']").matching_fn(test_result) 

492 ) 

493 

494 def test_matches(self): 

495 test_result = baker.make(TestResult, ts_run__machine__name="gpu_123") 

496 self.assertTrue(QueryParserPython(TestResult, "machine_name MATCHES 'gpu'").matching_fn(test_result)) 

497 self.assertTrue(QueryParserPython(TestResult, "machine_name ~= 'gpu'").matching_fn(test_result)) 

498 self.assertTrue(QueryParserPython(TestResult, "machine_name MATCHES 'gpu_\\d+'").matching_fn(test_result)) 

499 self.assertFalse(QueryParserPython(TestResult, "machine_name MATCHES 'gpu$'").matching_fn(test_result)) 

500 

501 def test_or_operator(self): 

502 test_result = baker.make(TestResult, status__name="fail") 

503 self.assertTrue( 

504 QueryParserPython(TestResult, "status_name = 'fail' OR status_name = 'pass'").matching_fn(test_result) 

505 ) 

506 self.assertTrue( 

507 QueryParserPython(TestResult, "status_name = 'pass' OR status_name = 'fail'").matching_fn(test_result) 

508 ) 

509 self.assertFalse( 

510 QueryParserPython(TestResult, "status_name = 'pass' OR status_name = 'abort'").matching_fn(test_result) 

511 ) 

512 

513 def test_or_operator__triple(self): 

514 test_result = baker.make(TestResult, status__name="fail") 

515 self.assertTrue( 

516 QueryParserPython( 

517 TestResult, "status_name = 'fail' OR status_name = 'pass' OR status_name = 'abort'" 

518 ).matching_fn(test_result) 

519 ) 

520 self.assertTrue( 

521 QueryParserPython( 

522 TestResult, "status_name = 'pass' OR status_name = 'fail' OR status_name = 'abort'" 

523 ).matching_fn(test_result) 

524 ) 

525 self.assertTrue( 

526 QueryParserPython( 

527 TestResult, "status_name = 'pass' OR status_name = 'abort' OR status_name = 'fail'" 

528 ).matching_fn(test_result) 

529 ) 

530 

531 def test_or_operator__multiple_fields(self): 

532 test_result = baker.make(TestResult, status__name="fail", test__name="test_1") 

533 self.assertTrue( 

534 QueryParserPython(TestResult, "status_name = 'fail' OR test_name = 'test_1'").matching_fn(test_result) 

535 ) 

536 self.assertTrue( 

537 QueryParserPython(TestResult, "status_name = 'pass' OR test_name = 'test_1'").matching_fn(test_result) 

538 ) 

539 self.assertTrue( 

540 QueryParserPython(TestResult, "status_name = 'fail' OR test_name = 'test_2'").matching_fn(test_result) 

541 ) 

542 self.assertFalse( 

543 QueryParserPython(TestResult, "status_name = 'pass' OR test_name = 'test_2'").matching_fn(test_result) 

544 ) 

545 

546 def test_and_operator(self): 

547 test_result = baker.make(TestResult, status__name="fail") 

548 self.assertTrue( 

549 QueryParserPython(TestResult, "status_name = 'fail' AND status_name = 'fail'").matching_fn(test_result) 

550 ) 

551 self.assertFalse( 

552 QueryParserPython(TestResult, "status_name = 'pass' AND status_name = 'fail'").matching_fn(test_result) 

553 ) 

554 self.assertFalse( 

555 QueryParserPython(TestResult, "status_name = 'fail' AND status_name = 'pass'").matching_fn(test_result) 

556 ) 

557 

558 def test_and_operator__triple(self): 

559 test_result = baker.make(TestResult, status__name="fail") 

560 self.assertTrue( 

561 QueryParserPython( 

562 TestResult, "status_name = 'fail' AND status_name = 'fail' AND status_name = 'fail'" 

563 ).matching_fn(test_result) 

564 ) 

565 self.assertFalse( 

566 QueryParserPython( 

567 TestResult, "status_name = 'pass' AND status_name = 'fail' AND status_name = 'fail'" 

568 ).matching_fn(test_result) 

569 ) 

570 

571 def test_and_operator__multiple_fields(self): 

572 test_result = baker.make(TestResult, status__name="fail", test__name="test_1") 

573 self.assertTrue( 

574 QueryParserPython(TestResult, "status_name = 'fail' AND test_name = 'test_1'").matching_fn(test_result) 

575 ) 

576 self.assertFalse( 

577 QueryParserPython(TestResult, "status_name = 'pass' AND test_name = 'test_1'").matching_fn(test_result) 

578 ) 

579 self.assertFalse( 

580 QueryParserPython(TestResult, "status_name = 'fail' AND test_name = 'test_2'").matching_fn(test_result) 

581 ) 

582 

583 def test_nested(self): 

584 test_result = baker.make( 

585 TestResult, 

586 ts_run__runconfig__name="run_1", 

587 ts_run__runconfig__tags=[baker.make(RunConfigTag, name="tag_1"), baker.make(RunConfigTag, name="tag_2")], 

588 ) 

589 self.assertTrue( 

590 QueryParserPython( 

591 TestResult, "runconfig MATCHES (name='run_1' AND tag CONTAINS ['tag_1', 'tag_2'])" 

592 ).matching_fn(test_result) 

593 ) 

594 self.assertFalse( 

595 QueryParserPython( 

596 TestResult, "runconfig MATCHES (name='run_1' AND tag CONTAINS ['tag_1', 'tag_none'])" 

597 ).matching_fn(test_result) 

598 ) 

599 

600 def test_less_than(self): 

601 issue = baker.make(Issue, id=1) 

602 self.assertTrue(QueryParserPython(Issue, "id < 2").matching_fn(issue)) 

603 self.assertFalse(QueryParserPython(Issue, "id < 1").matching_fn(issue)) 

604 

605 def test_less_than_equal(self): 

606 issue = baker.make(Issue, id=1) 

607 self.assertTrue(QueryParserPython(Issue, "id <= 2").matching_fn(issue)) 

608 self.assertTrue(QueryParserPython(Issue, "id <= 1").matching_fn(issue)) 

609 self.assertFalse(QueryParserPython(Issue, "id <= 0").matching_fn(issue)) 

610 

611 def test_greater_than(self): 

612 issue = baker.make(Issue, id=1) 

613 self.assertTrue(QueryParserPython(Issue, "id > 0").matching_fn(issue)) 

614 self.assertFalse(QueryParserPython(Issue, "id > 1").matching_fn(issue)) 

615 

616 def test_greater_than_equal(self): 

617 issue = baker.make(Issue, id=1) 

618 self.assertTrue(QueryParserPython(Issue, "id >= 0").matching_fn(issue)) 

619 self.assertTrue(QueryParserPython(Issue, "id >= 1").matching_fn(issue)) 

620 self.assertFalse(QueryParserPython(Issue, "id >= 2").matching_fn(issue)) 

621 

622 def test_reusability(self): 

623 test_fail = baker.make(TestResult, status__name="fail") 

624 test_abort = baker.make(TestResult, status__name="abort") 

625 test_pass = baker.make(TestResult, status__name="pass") 

626 test_results = [test_fail, test_abort, test_pass] 

627 matches = QueryParserPython(TestResult, "status_name IS IN ['fail', 'abort']").matching_fn 

628 filtered_list = list(filter(matches, test_results)) 

629 self.assertEqual(filtered_list, [test_fail, test_abort]) 

630 

631 def test_ignore_fields(self): 

632 test_result = baker.make(TestResult, status__name="fail", stdout="") 

633 self.assertFalse( 

634 QueryParserPython(TestResult, "status_name = 'fail' AND stdout ~= 'err'").matching_fn(test_result) 

635 ) 

636 self.assertTrue( 

637 QueryParserPython( 

638 TestResult, "status_name = 'fail' AND stdout ~= 'err'", ignore_fields=["stdout"] 

639 ).matching_fn(test_result) 

640 ) 

641 

642 def test_brackets(self): 

643 query = """((testsuite_name = "testsuite1" AND status_name IS IN ["fail", "abort"]) 

644 OR (testsuite_name = "testsuite2" AND status_name ="skip"))""" 

645 testsuite1 = baker.make(TestSuite, name="testsuite1") 

646 testsuite2 = baker.make(TestSuite, name="testsuite2") 

647 test_result1 = baker.make(TestResult, status__name="fail", status__testsuite=testsuite1) 

648 test_result2 = baker.make(TestResult, status__name="fail", status__testsuite=testsuite2) 

649 test_result3 = baker.make(TestResult, status__name="skip", status__testsuite=testsuite2) 

650 self.assertTrue(QueryParserPython(TestResult, query).matching_fn(test_result1)) 

651 self.assertFalse(QueryParserPython(TestResult, query).matching_fn(test_result2)) 

652 self.assertTrue(QueryParserPython(TestResult, query).matching_fn(test_result3)) 

653 

654 

655class QueryParsersCompilanceTests(TestCase): 

656 def assert_parsers_compilance(self, model, user_query, test_results: list, noise_results: list = []): 

657 for test_result in test_results: 

658 self.assertTrue(QueryParserPython(model, user_query).matching_fn(test_result)) 

659 self.assertIn(test_result, QueryParser(model, user_query).objects) 

660 self.assertEqual(QueryParser(model, user_query).objects.count(), len(test_results)) 

661 

662 for noise_result in noise_results: 

663 self.assertFalse(QueryParserPython(model, user_query).matching_fn(noise_result)) 

664 

665 def test_empty(self): 

666 test_result = baker.make(TestResult) 

667 user_query = "" 

668 self.assert_parsers_compilance(TestResult, user_query, [test_result]) 

669 

670 def test_equal_string(self): 

671 test_result = baker.make(TestResult, status__name="pass") 

672 noise_result = baker.make(TestResult, status__name="fail") 

673 self.assert_parsers_compilance(TestResult, "status_name = 'pass'", [test_result], [noise_result]) 

674 self.assert_parsers_compilance(TestResult, "status_name = 'abort'", [], [noise_result]) 

675 

676 def test_equal_empty_string(self): 

677 noise_results = [baker.make(TestResult, status__name="pass")] 

678 self.assert_parsers_compilance(TestResult, "status_name = ''", [], noise_results) 

679 

680 def test_equal_boolen(self): 

681 test_model = baker.make(TestModel, boolean=True) 

682 noise_model = baker.make(TestModel, boolean=False) 

683 self.assert_parsers_compilance(TestModel, "boolean = TRUE", [test_model], [noise_model]) 

684 

685 def test_equal_integer(self): 

686 test_model = baker.make(TestModel, number=123) 

687 noise_model = baker.make(TestModel, number=456) 

688 self.assert_parsers_compilance(TestModel, "number = 123", [test_model], [noise_model]) 

689 

690 def test_equal_datetime(self): 

691 test_model = baker.make(TestModel, date=datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)) 

692 noise_model = baker.make(TestModel, date=datetime.datetime(2000, 1, 2, tzinfo=pytz.utc)) 

693 self.assert_parsers_compilance(TestModel, "date = datetime(2000-01-01)", [test_model], [noise_model]) 

694 

695 def test_equal_duration(self): 

696 test_model = baker.make(TestModel, duration=datetime.timedelta(seconds=60)) 

697 noise_model = baker.make(TestModel, duration=datetime.timedelta(seconds=120)) 

698 self.assert_parsers_compilance(TestModel, "duration = duration(00:01:00)", [test_model], [noise_model]) 

699 

700 def test_equal_json(self): 

701 test_model = baker.make(TestModel, json={"key": "value"}) 

702 noise_models = [baker.make(TestModel, json={"key": "noise"}), baker.make(TestModel, json={"foo": "bar"})] 

703 self.assert_parsers_compilance(TestModel, "json.key = 'value'", [test_model], noise_models) 

704 

705 def test_equal_m2m(self): 

706 test_results = [ 

707 baker.make( 

708 TestResult, 

709 ts_run__machine__tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")], 

710 ) 

711 ] 

712 noise_results = [baker.make(TestResult, ts_run__machine__tags=[baker.make(MachineTag, name="tag_noise")])] 

713 self.assert_parsers_compilance(TestResult, "machine_tag = 'tag1'", test_results, noise_results) 

714 self.assert_parsers_compilance(TestResult, "machine_tag = 'tag2'", test_results, noise_results) 

715 self.assert_parsers_compilance(TestResult, "machine_tag = 'tag3'", [], noise_results) 

716 

717 def test_equal_m2m_multiple(self): 

718 machine_tag_1 = baker.make(MachineTag, name="tag1") 

719 test_results = [ 

720 baker.make( 

721 TestResult, 

722 ts_run__machine__tags=[machine_tag_1, baker.make(MachineTag, name="tag2")], 

723 ) 

724 ] 

725 noise_results = [baker.make(TestResult, ts_run__machine__tags=[machine_tag_1])] 

726 self.assert_parsers_compilance( 

727 TestResult, "machine_tag = 'tag1' AND machine_tag = 'tag2'", test_results, noise_results 

728 ) 

729 self.assert_parsers_compilance(TestResult, "machine_tag = 'tag1' AND machine_tag = 'tag3'", [], noise_results) 

730 

731 def test_not_equal_query(self): 

732 test_results = [baker.make(TestResult, status__name="pass")] 

733 noise_results = [baker.make(TestResult, status__name="fail")] 

734 self.assert_parsers_compilance(TestResult, "status_name != 'fail'", test_results, noise_results) 

735 

736 def test_not_prefix(self): 

737 test_results = [baker.make(TestResult, status__name="pass")] 

738 noise_results = [baker.make(TestResult, status__name="fail")] 

739 self.assert_parsers_compilance(TestResult, "NOT status_name = 'fail'", test_results, noise_results) 

740 

741 def test_contains(self): 

742 test_results = [baker.make(TestResult, status__name="pass")] 

743 noise_results = [baker.make(TestResult, status__name="fail")] 

744 self.assert_parsers_compilance(TestResult, "status_name CONTAINS 'ss'", test_results, noise_results) 

745 self.assert_parsers_compilance(TestResult, "status_name CONTAINS 'xx'", [], noise_results) 

746 

747 def test_icontains(self): 

748 test_results = [baker.make(TestResult, status__name="pass")] 

749 noise_results = [baker.make(TestResult, status__name="fail")] 

750 self.assert_parsers_compilance(TestResult, "status_name ICONTAINS 'SS'", test_results, noise_results) 

751 

752 def test_is_in(self): 

753 test_results = [baker.make(TestResult, status__name="pass")] 

754 noise_results = [baker.make(TestResult, status__name="abort")] 

755 self.assert_parsers_compilance(TestResult, "status_name IS IN ['pass', 'fail']", test_results, noise_results) 

756 self.assert_parsers_compilance(TestResult, "status_name IS IN ['fail']", [], noise_results) 

757 

758 def test_not_in(self): 

759 test_results = [baker.make(TestResult, status__name="pass")] 

760 noise_results = [baker.make(TestResult, status__name="fail")] 

761 self.assert_parsers_compilance(TestResult, "status_name NOT IN ['fail', 'abort']", test_results, noise_results) 

762 self.assert_parsers_compilance(TestResult, "status_name NOT IN ['fail', 'pass']", [], noise_results) 

763 

764 def test_is_in_m2m(self): 

765 test_results = [ 

766 baker.make( 

767 TestResult, 

768 ts_run__machine__tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")], 

769 ) 

770 ] 

771 noise_results = [baker.make(TestResult, ts_run__machine__tags=[baker.make(MachineTag, name="tag4")])] 

772 self.assert_parsers_compilance(TestResult, "machine_tag IS IN ['tag1']", test_results, noise_results) 

773 self.assert_parsers_compilance( 

774 TestResult, "machine_tag IS IN ['tag1', 'tag2', 'tag3']", test_results, noise_results 

775 ) 

776 self.assert_parsers_compilance(TestResult, "machine_tag IS IN ['tag3']", [], noise_results) 

777 

778 def test_contains_m2m(self): 

779 test_results = [ 

780 baker.make( 

781 TestResult, 

782 ts_run__machine__tags=[baker.make(MachineTag, name="tag1"), baker.make(MachineTag, name="tag2")], 

783 ) 

784 ] 

785 noise_results = [baker.make(TestResult, ts_run__machine__tags=[baker.make(MachineTag, name="tag3")])] 

786 self.assert_parsers_compilance(TestResult, "machine_tag CONTAINS ['tag1']", test_results, noise_results) 

787 self.assert_parsers_compilance(TestResult, "machine_tag CONTAINS ['tag1', 'tag2']", test_results, noise_results) 

788 self.assert_parsers_compilance(TestResult, "machine_tag CONTAINS ['tag1', 'tag2', 'tag3']", []) 

789 

790 def test_matches(self): 

791 test_result_1 = baker.make(TestResult, ts_run__machine__name="gpu") 

792 test_result_2 = baker.make(TestResult, ts_run__machine__name="gpu_123") 

793 noise_results = [baker.make(TestResult, ts_run__machine__name="cpu")] 

794 

795 self.assert_parsers_compilance( 

796 TestResult, "machine_name MATCHES 'gpu'", [test_result_1, test_result_2], noise_results 

797 ) 

798 self.assert_parsers_compilance( 

799 TestResult, "machine_name ~= 'gpu'", [test_result_1, test_result_2], noise_results 

800 ) 

801 self.assert_parsers_compilance(TestResult, "machine_name MATCHES 'gpu$'", [test_result_1], noise_results) 

802 self.assert_parsers_compilance(TestResult, "machine_name MATCHES 'gpu_\\d+'", [test_result_2], noise_results) 

803 self.assert_parsers_compilance(TestResult, "machine_name MATCHES 'foo'", [], noise_results) 

804 

805 def test_or_operator(self): 

806 test_result_1 = baker.make(TestResult, status__name="pass") 

807 test_result_2 = baker.make(TestResult, status__name="fail") 

808 noise_results = [baker.make(TestResult, status__name="foo")] 

809 self.assert_parsers_compilance( 

810 TestResult, "status_name = 'fail' OR status_name = 'pass'", [test_result_1, test_result_2], noise_results 

811 ) 

812 self.assert_parsers_compilance( 

813 TestResult, "status_name = 'pass' OR status_name = 'fail'", [test_result_1, test_result_2], noise_results 

814 ) 

815 self.assert_parsers_compilance( 

816 TestResult, "status_name = 'pass' OR status_name = 'abort'", [test_result_1], noise_results 

817 ) 

818 self.assert_parsers_compilance( 

819 TestResult, "status_name = 'fail' OR status_name = 'abort'", [test_result_2], noise_results 

820 ) 

821 self.assert_parsers_compilance(TestResult, "status_name = 'skip' OR status_name = 'abort'", [], noise_results) 

822 

823 def test_or_operator__triple(self): 

824 test_results = [baker.make(TestResult, status__name="pass")] 

825 noise_results = [baker.make(TestResult, status__name="foo")] 

826 self.assert_parsers_compilance( 

827 TestResult, 

828 "status_name = 'fail' OR status_name = 'pass' OR status_name = 'abort'", 

829 test_results, 

830 noise_results, 

831 ) 

832 self.assert_parsers_compilance( 

833 TestResult, 

834 "status_name = 'pass' OR status_name = 'fail' OR status_name = 'abort'", 

835 test_results, 

836 noise_results, 

837 ) 

838 self.assert_parsers_compilance( 

839 TestResult, 

840 "status_name = 'pass' OR status_name = 'abort' OR status_name = 'fail'", 

841 test_results, 

842 noise_results, 

843 ) 

844 

845 def test_or_operator__multiple_fields(self): 

846 test_results = [baker.make(TestResult, status__name="fail", test__name="test_1")] 

847 noise_results = [baker.make(TestResult, status__name="foo")] 

848 self.assert_parsers_compilance( 

849 TestResult, "status_name = 'fail' OR test_name = 'test_1'", test_results, noise_results 

850 ) 

851 self.assert_parsers_compilance( 

852 TestResult, "status_name = 'pass' OR test_name = 'test_1'", test_results, noise_results 

853 ) 

854 self.assert_parsers_compilance( 

855 TestResult, "status_name = 'fail' OR test_name = 'test_2'", test_results, noise_results 

856 ) 

857 self.assert_parsers_compilance(TestResult, "status_name = 'pass' OR test_name = 'test_2'", [], noise_results) 

858 

859 def test_and_operator(self): 

860 test_results = [baker.make(TestResult, status__name="fail")] 

861 noise_results = [baker.make(TestResult, status__name="foo")] 

862 self.assert_parsers_compilance( 

863 TestResult, "status_name = 'fail' AND status_name = 'fail'", test_results, noise_results 

864 ) 

865 self.assert_parsers_compilance(TestResult, "status_name = 'pass' AND status_name = 'fail'", [], noise_results) 

866 self.assert_parsers_compilance(TestResult, "status_name = 'fail' AND status_name = 'pass'", [], noise_results) 

867 

868 def test_and_operator__triple(self): 

869 test_results = [baker.make(TestResult, status__name="fail")] 

870 noise_results = [baker.make(TestResult, status__name="foo")] 

871 self.assert_parsers_compilance( 

872 TestResult, 

873 "status_name = 'fail' AND status_name = 'fail' AND status_name = 'fail'", 

874 test_results, 

875 noise_results, 

876 ) 

877 self.assert_parsers_compilance( 

878 TestResult, "status_name = 'pass' AND status_name = 'fail' AND status_name = 'fail'", [], noise_results 

879 ) 

880 

881 def test_and_operator__multiple_fields(self): 

882 test_results = [baker.make(TestResult, status__name="fail", test__name="test_1")] 

883 noise_results = [baker.make(TestResult, status__name="foo")] 

884 self.assert_parsers_compilance( 

885 TestResult, "status_name = 'fail' AND test_name = 'test_1'", test_results, noise_results 

886 ) 

887 self.assert_parsers_compilance(TestResult, "status_name = 'pass' AND test_name = 'test_1'", [], noise_results) 

888 self.assert_parsers_compilance(TestResult, "status_name = 'fail' AND test_name = 'test_2'", [], noise_results) 

889 

890 def test_nested(self): 

891 test_results = [ 

892 baker.make( 

893 TestResult, 

894 ts_run__runconfig__name="run_1", 

895 ts_run__runconfig__tags=[ 

896 baker.make(RunConfigTag, name="tag_1"), 

897 baker.make(RunConfigTag, name="tag_2"), 

898 ], 

899 ) 

900 ] 

901 noise_results = [baker.make(TestResult, ts_run__runconfig__name="run_foo")] 

902 self.assert_parsers_compilance( 

903 TestResult, 

904 "runconfig MATCHES (name='run_1' AND tag CONTAINS ['tag_1', 'tag_2'])", 

905 test_results, 

906 noise_results, 

907 ) 

908 self.assert_parsers_compilance( 

909 TestResult, "runconfig MATCHES (name='run_1' AND tag CONTAINS ['tag_1', 'tag_none'])", [], noise_results 

910 ) 

911 

912 def test_less_than_number(self): 

913 issue_1 = baker.make(Issue, id=1) 

914 issue_2 = baker.make(Issue, id=2) 

915 self.assert_parsers_compilance(Issue, "id < 3", [issue_1, issue_2]) 

916 self.assert_parsers_compilance(Issue, "id < 2", [issue_1]) 

917 self.assert_parsers_compilance(Issue, "id < 1", []) 

918 

919 def test_less_than_duration(self): 

920 test_model_1 = baker.make(TestModel, duration=datetime.timedelta(seconds=30)) 

921 test_model_2 = baker.make(TestModel, duration=datetime.timedelta(seconds=60)) 

922 self.assert_parsers_compilance(TestModel, "duration < duration(00:02:00)", [test_model_1, test_model_2]) 

923 self.assert_parsers_compilance(TestModel, "duration < duration(00:01:00)", [test_model_1]) 

924 self.assert_parsers_compilance(TestModel, "duration < duration(00:00:30)", []) 

925 

926 def test_less_than_datetime(self): 

927 test_model_1 = baker.make(TestModel, date=datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)) 

928 test_model_2 = baker.make(TestModel, date=datetime.datetime(2000, 1, 2, tzinfo=pytz.utc)) 

929 self.assert_parsers_compilance(TestModel, "date < datetime(2000-01-03)", [test_model_1, test_model_2]) 

930 self.assert_parsers_compilance(TestModel, "date < datetime(2000-01-02)", [test_model_1]) 

931 self.assert_parsers_compilance(TestModel, "date < datetime(2000-01-01)", []) 

932 

933 def test_less_than_equal_number(self): 

934 issue_1 = baker.make(Issue, id=1) 

935 issue_2 = baker.make(Issue, id=2) 

936 self.assert_parsers_compilance(Issue, "id <= 3", [issue_1, issue_2]) 

937 self.assert_parsers_compilance(Issue, "id <= 2", [issue_1, issue_2]) 

938 self.assert_parsers_compilance(Issue, "id <= 1", [issue_1]) 

939 self.assert_parsers_compilance(Issue, "id <= 0", []) 

940 

941 def test_greater_than(self): 

942 issue_1 = baker.make(Issue, id=1) 

943 issue_2 = baker.make(Issue, id=2) 

944 self.assert_parsers_compilance(Issue, "id > 0", [issue_1, issue_2]) 

945 self.assert_parsers_compilance(Issue, "id > 1", [issue_2]) 

946 self.assert_parsers_compilance(Issue, "id > 2", []) 

947 

948 def test_greater_than_equal(self): 

949 issue_1 = baker.make(Issue, id=1) 

950 issue_2 = baker.make(Issue, id=2) 

951 self.assert_parsers_compilance(Issue, "id >= 0", [issue_1, issue_2]) 

952 self.assert_parsers_compilance(Issue, "id >= 1", [issue_1, issue_2]) 

953 self.assert_parsers_compilance(Issue, "id >= 2", [issue_2]) 

954 self.assert_parsers_compilance(Issue, "id >= 3", []) 

955 

956 def test_brackets(self): 

957 query = """((testsuite_name = "testsuite1" AND status_name IS IN ["fail", "abort"]) 

958 OR (testsuite_name = "testsuite2" AND status_name ="skip"))""" 

959 testsuite1 = baker.make(TestSuite, name="testsuite1") 

960 testsuite2 = baker.make(TestSuite, name="testsuite2") 

961 test_result1 = baker.make(TestResult, status__name="fail", status__testsuite=testsuite1) 

962 test_result2 = baker.make(TestResult, status__name="skip", status__testsuite=testsuite2) 

963 noise_results = [baker.make(TestResult, status__name="fail", status__testsuite=testsuite2)] 

964 

965 self.assert_parsers_compilance(TestResult, query, [test_result1, test_result2], noise_results) 

966 

967 

968class LegacyParserTests(TestCase): 

969 def setUp(self): 

970 UserFiltrableMixin.filter_objects_to_db = { 

971 "user_abc": FilterObjectStr('db__abc'), 

972 "user_def": FilterObjectStr('db__def'), 

973 "user_ghi": FilterObjectStr('db__ghi'), 

974 "user_jkl": FilterObjectBool('db__jkl'), 

975 "user_mno": FilterObjectDuration('db__mno'), 

976 } 

977 

978 def test_no_filters(self): 

979 parser = LegacyParser(UserFiltrableMixin) 

980 self.assertEqual(parser.query, "") 

981 

982 def test_valid_filters(self): 

983 parser = LegacyParser(UserFiltrableMixin, 

984 only__user_abc__in=['toto', 'int(1234.3)'], 

985 only__user_def__exact='datetime(2018-06-23)', 

986 only__user_ghi__gt=['int(456)'], 

987 only__user_jkl__exact='bool(1)', 

988 only__user_mno__exact='duration(00:00:03)', 

989 exclude__user_def__regex='str(hello)') 

990 self.assertEqual(parser.query, 

991 "user_abc IS IN ['toto', 1234.3] AND user_def = datetime(2018-06-23) AND user_ghi > 456 " 

992 "AND user_jkl = TRUE AND user_mno = duration(00:00:03) AND NOT (user_def ~= 'hello')") 

993 

994 def test_regex_aggregation(self): 

995 parser = LegacyParser(UserFiltrableMixin, only__user_abc__regex=['toto', 'tata', 'titi']) 

996 self.assertEqual(parser.query, "user_abc ~= '(toto|tata|titi)'") 

997 

998 def test_invalid_formats(self): 

999 parser = LegacyParser(UserFiltrableMixin, balbla='ghujfdk', oops__user_abc__in=12, 

1000 only__invalid__in=13, only__user_abc__toto=14) 

1001 self.assertEqual(parser.query, "") 

1002 

1003 

1004class UserFiltrableMixinTests(TestCase): 

1005 def test_old_style(self): 

1006 queryset = TestResult.from_user_filters(only__status_name__exact='pass').objects 

1007 self.assertIn('WHERE "CIResults_textstatus"."name" = pass', str(queryset.query)) 

1008 

1009 def test_new_style(self): 

1010 queryset = TestResult.from_user_filters(query=['status_name = "pass"']).objects 

1011 self.assertIn('WHERE "CIResults_textstatus"."name" = pass', str(queryset.query)) 

1012 

1013 def test_new_style_with_short_queries(self): 

1014 q = 'status_name = "toto"' 

1015 q2 = 'status_name = "tata"' 

1016 

1017 # Check that the shorthand versions resolve to the right query 

1018 short_query = Shortener.get_or_create(q) 

1019 query = TestResult.from_user_filters(query_key=short_query.shorthand) 

1020 self.assertEqual(query.user_query, q) 

1021 

1022 # Check that we prioritize full queries to shorthands 

1023 query = TestResult.from_user_filters(query=q2, query_key=short_query.shorthand) 

1024 self.assertEqual(query.user_query, q2) 

1025 

1026 def test_sub_queries(self): 

1027 parser = TestResult.from_user_filters(query=['machine_tag CONTAINS ["tag1", "tag2"]']) 

1028 sub_query = str(parser.q_objects.children[0][1].query) 

1029 self.assertEqual(parser.q_objects.children[0][0], 'ts_run__machine__in') 

1030 self.assertIn('"name" = tag1', sub_query) 

1031 self.assertIn('"name" = tag2', sub_query) 

1032 

1033 

1034class FilterObjectTests(TestCase): 

1035 def test_empty_description(self): 

1036 self.assertEqual(FilterObject("").description, "<no description yet>") 

1037 

1038 def test_with_description(self): 

1039 self.assertEqual(FilterObject("", "My description").description, "My description") 

1040 

1041 

1042class FilterObjectDurationTests(TestCase): 

1043 def test_invalid_value(self): 

1044 with self.assertRaisesRegex(ValueError, "The value '1 month' does not represent a duration"): 

1045 FilterObjectDuration.parse_value('1 month') 

1046 

1047 

1048class BuildQueryFromRequestTests(TestCase): 

1049 def setUp(self): 

1050 self.factory = RequestFactory() 

1051 

1052 def test_build_machine_query_from_request(self): 

1053 request = self.factory.get('/machine?name=name&description=description') 

1054 self.assertEqual(QueryCreator(request, Machine).multiple_request_params_to_query().user_query, 

1055 "name MATCHES 'name' AND description MATCHES 'description'")