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

404 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-09 12:54 +0000

1from unittest import mock 

2from unittest.mock import patch, Mock, call, mock_open, MagicMock 

3from django.core.management import call_command 

4from django.test import TestCase 

5from django.utils import timezone 

6from io import StringIO 

7 

8from CIResults.models import Component, Build, Test, Machine, RunConfig, TestSuite, TestsuiteRun 

9from CIResults.models import TextStatus, TestResult, KnownFailure, RunFilterStatistic, UnknownFailure 

10from CIResults.models import IssueFilter, Issue, IssueFilterAssociated 

11from CIResults.run_import import ( 

12 RunConfigResultsFromArgs, RunConfigResultsFromDir, TestsuiteRunResults, TestsuiteTestResult, TestsuiteResults, 

13 RunConfigResults, PiglitResult, TestSuiteRunDef, ResultsCommitHandler, JsonResult, 

14) 

15from add_build import BuildResult 

16 

17import configparser 

18import datetime 

19 

20 

21class BuildResultTests(TestCase): 

22 

23 def __mock_configparser__(self): 

24 # Replace the configparser instanciated by RunConfigResult with our own 

25 self.config = configparser.ConfigParser() 

26 patcher_api_call = patch('configparser.ConfigParser') 

27 mock_matches = patcher_api_call.start() 

28 mock_matches.return_value = self.config 

29 self.addCleanup(patcher_api_call.stop) 

30 

31 # Make the read() function a noop 

32 self.config.read = lambda x: 0 

33 

34 def test_parse_run_info__empty(self): 

35 self.__mock_configparser__() 

36 

37 # Check empty 

38 self.config.read_string("") 

39 self.assertRaisesMessage(ValueError, 

40 "The build info file build.ini is invalid: missing the section CIRESULTS_BUILD", 

41 BuildResult.from_build_ini, "") 

42 

43 def test_parse_run_info__more_sections(self): 

44 self.__mock_configparser__() 

45 

46 cfg = """[CIRESULTS_BUILD] 

47 field = value 

48 

49 [SECTION] 

50 field = value""" 

51 self.config.read_string(cfg) 

52 msg = "The build info file build.ini is invalid: only the section CIRESULTS_BUILD is allowed" 

53 self.assertRaisesMessage(ValueError, msg, BuildResult.from_build_ini, "") 

54 

55 def test_parse_run_info__minimal(self): 

56 self.__mock_configparser__() 

57 

58 component = Component.objects.create(name="COMPONENT1", description="desc", 

59 url="url", public=True) 

60 

61 cfg = """[CIRESULTS_BUILD] 

62 name: BUILD2 

63 component: COMPONENT1 

64 version: VERSION1""" 

65 self.config.read_string(cfg) 

66 build = BuildResult.from_build_ini("") 

67 build.commit_to_db() 

68 

69 obj = Build.objects.get(name="BUILD2") 

70 self.assertEqual(obj.name, "BUILD2") 

71 self.assertEqual(obj.component, component) 

72 self.assertEqual(obj.version, "VERSION1") 

73 self.assertEqual(set(obj.parents.all()), set()) 

74 self.assertEqual(obj.branch, None) 

75 self.assertEqual(obj.repo_type, None) 

76 self.assertEqual(obj.repo, None) 

77 self.assertEqual(obj.upstream_url, None) # Check that the invalid URL was not used 

78 self.assertEqual(obj.parameters, None) 

79 self.assertEqual(obj.build_log, None) 

80 

81 @patch('builtins.open', mock_open(read_data="Parameters\nfile")) 

82 def test_parse_run_info__parameters_file(self): 

83 self.__mock_configparser__() 

84 

85 Component.objects.create(name="COMPONENT1", description="desc", url="url", public=True) 

86 

87 cfg = """[CIRESULTS_BUILD] 

88 name: BUILD1 

89 component: COMPONENT1 

90 version: VERSION1 

91 parameters_file: file""" 

92 self.config.read_string(cfg) 

93 build = BuildResult.from_build_ini("") 

94 build.commit_to_db() 

95 

96 obj = Build.objects.get(name="BUILD1") 

97 self.assertEqual(obj.parameters, "Parameters\nfile") 

98 

99 @patch('builtins.open', mock_open(read_data="Parameters\nfile")) 

100 def test_parse_run_info__parameters_priority(self): 

101 self.__mock_configparser__() 

102 

103 Component.objects.create(name="COMPONENT1", description="desc", url="url", public=True) 

104 

105 cfg = """[CIRESULTS_BUILD] 

106 name: BUILD1 

107 component: COMPONENT1 

108 version: VERSION1 

109 parameters: parameters inline 

110 parameters_file: file""" 

111 self.config.read_string(cfg) 

112 build = BuildResult.from_build_ini("") 

113 build.commit_to_db() 

114 

115 obj = Build.objects.get(name="BUILD1") 

116 self.assertEqual(obj.parameters, "parameters inline") 

117 

118 @patch('builtins.open', mock_open(read_data="My\nBuild\nLog")) 

119 def test_parse_run_info__complete(self): 

120 self.__mock_configparser__() 

121 

122 # Create some objects in the DB that are dependencies for the new build 

123 component = Component.objects.create(name="COMPONENT1", description="desc", 

124 url="url", public=True) 

125 builds = [] 

126 for i in range(0, 2): 

127 b = Build.objects.create(name="BUILD{}".format(i), component=component, 

128 version="version") 

129 builds.append(b) 

130 

131 # Check a complete configuration 

132 cfg = """[CIRESULTS_BUILD] 

133 name: BUILD2 

134 component: COMPONENT1 

135 version: VERSION1 

136 parents: BUILD0 BUILD1 

137 branch: BRANCH1 

138 repo_type: REPO_TYPE1 

139 repo: http://repo.example 

140 upstream_url: http://URL1.de 

141 parameters: PARAMETERS WITH SPACES 

142 AND 

143 NEW 

144 LINES 

145 build_log_file: logfile 

146 """ 

147 self.config.read_string(cfg) 

148 build = BuildResult.from_build_ini("") 

149 build.commit_to_db() 

150 

151 obj = Build.objects.get(name="BUILD2") 

152 self.assertEqual(obj.name, "BUILD2") 

153 self.assertEqual(obj.component, component) 

154 self.assertEqual(obj.version, "VERSION1") 

155 self.assertEqual(set(obj.parents.all()), set(builds)) 

156 self.assertEqual(obj.branch, "BRANCH1") 

157 self.assertEqual(obj.repo_type, "REPO_TYPE1") 

158 self.assertEqual(obj.repo, "http://repo.example") 

159 self.assertEqual(obj.upstream_url, "http://URL1.de") 

160 self.assertEqual(obj.parameters, "PARAMETERS WITH SPACES\nAND\nNEW\nLINES") 

161 self.assertEqual(obj.build_log, "My\nBuild\nLog") 

162 

163 @patch('builtins.open', mock_open(read_data="My\nBuild\nLog")) 

164 def test_parse_run_info__no_ini__complete(self): 

165 # Create some objects in the DB that are dependencies for the new build 

166 component = Component.objects.create(name="COMPONENT1", description="desc", 

167 url="url", public=True) 

168 builds = [] 

169 for i in range(0, 2): 

170 b = Build.objects.create(name="BUILD{}".format(i), component=component, 

171 version="version") 

172 builds.append(b) 

173 

174 # Check a complete configuration 

175 build = BuildResult.from_dict(name="BUILD2", component="COMPONENT1", version="VERSION1", 

176 parents=["BUILD0", "BUILD1"], branch="BRANCH1", 

177 repo_type="REPO_TYPE1", repo="http://repo.example", 

178 upstream_url="http://URL1.de", 

179 parameters="PARAMETERS WITH SPACES\nAND\nNEW\nLINES", 

180 config_file="", build_log="My\nBuild\nLog") 

181 build.commit_to_db() 

182 

183 obj = Build.objects.get(name="BUILD2") 

184 self.assertEqual(obj.name, "BUILD2") 

185 self.assertEqual(obj.component, component) 

186 self.assertEqual(obj.version, "VERSION1") 

187 self.assertEqual(set(obj.parents.all()), set(builds)) 

188 self.assertEqual(obj.branch, "BRANCH1") 

189 self.assertEqual(obj.repo_type, "REPO_TYPE1") 

190 self.assertEqual(obj.repo, "http://repo.example") 

191 self.assertEqual(obj.upstream_url, "http://URL1.de") 

192 self.assertEqual(obj.parameters, "PARAMETERS WITH SPACES\nAND\nNEW\nLINES") 

193 self.assertEqual(obj.build_log, "My\nBuild\nLog") 

194 

195 def test_parse_run_info__no_ini__parents_is_None(self): 

196 # Create some objects in the DB that are dependencies for the new build 

197 component = Component.objects.create(name="COMPONENT1", description="desc", 

198 url="url", public=True) 

199 builds = [] 

200 for i in range(0, 2): 

201 b = Build.objects.create(name="BUILD{}".format(i), component=component, 

202 version="version") 

203 builds.append(b) 

204 

205 # Check a complete configuration 

206 build = BuildResult.from_dict(name="BUILD2", component="COMPONENT1", version="VERSION1", parents=[]) 

207 build.commit_to_db() 

208 

209 obj = Build.objects.get(name="BUILD2") 

210 self.assertEqual(obj.name, "BUILD2") 

211 self.assertEqual(obj.component, component) 

212 self.assertEqual(obj.version, "VERSION1") 

213 self.assertEqual(set(obj.parents.all()), set()) 

214 

215 

216class TestsuiteRunResultsTests(TestCase): 

217 def test___result_url__(self): 

218 pattern = "http://hello.world/{runconfig}/{machine}/{testsuite_build}/{run_id}/{test}" 

219 testsuite = Mock(build="build1", result_url_pattern=pattern, runconfig=Mock()) 

220 testsuite.runconfig.name = "runcfg" 

221 

222 self.assertEqual(TestsuiteRunResults.get_results_url(testsuite, 42, "machine1", "test1"), 

223 "http://hello.world/runcfg/machine1/build1/42/test1") 

224 

225 

226class TestsuiteResultsTests(TestCase): 

227 def setUp(self): 

228 self.runconfig = Mock(name="runcfg") 

229 

230 @patch('CIResults.models.Build.objects.get', return_value=Build(component=Component())) 

231 @patch('CIResults.models.TestSuite.objects.get', return_value=TestSuite()) 

232 def test_sanity_piglit(self, ts_mock, build_mock): 

233 ts_run = TestsuiteResults(self.runconfig, "name1", "build1", "", "piglit", 1) 

234 self.assertEqual(self.runconfig, self.runconfig) 

235 self.assertEqual(ts_run.name, "name1") 

236 self.assertEqual(ts_run.build, "build1") 

237 build_mock.assert_called_once() 

238 ts_mock.assert_called_once() 

239 self.assertEqual(type(ts_run.db_object), TestSuite) 

240 self.assertEqual(ts_run.format, "piglit") 

241 self.assertEqual(ts_run.format_version, 1) 

242 self.assertEqual(ts_run._result_type, PiglitResult) 

243 

244 @patch('CIResults.models.Build.objects.get', return_value=Build(component=Component())) 

245 @patch('CIResults.models.TestSuite.objects.get', return_value=TestSuite()) 

246 def test_sanity_json(self, ts_mock, build_mock): 

247 ts_run = TestsuiteResults(self.runconfig, "name1", "build1", "", "json") 

248 self.assertEqual(self.runconfig, self.runconfig) 

249 self.assertEqual(ts_run.name, "name1") 

250 self.assertEqual(ts_run.build, "build1") 

251 build_mock.assert_called_once() 

252 ts_mock.assert_called_once() 

253 self.assertEqual(type(ts_run.db_object), TestSuite) 

254 self.assertEqual(ts_run.format, "json") 

255 self.assertEqual(ts_run.format_version, None) 

256 self.assertEqual(ts_run._result_type, JsonResult) 

257 

258 @patch('CIResults.models.Build.objects.get', return_value=Build(component=Component())) 

259 @patch('CIResults.models.TestSuite.objects.get', return_value=TestSuite()) 

260 def test_invalid_format(self, ts_mock, build_mock): 

261 self.assertRaisesMessage(ValueError, "The testsuite result format 'nonexisting' is unsupported", 

262 TestsuiteResults, self.runconfig, "name1", "build1", "", "nonexisting", 1) 

263 

264 self.assertRaisesMessage(ValueError, "The version 0 of the testsuite result format 'piglit' is unsupported", 

265 TestsuiteResults, self.runconfig, "name1", "build1", "", "piglit", 0) 

266 

267 @patch('CIResults.models.Build.objects.get', return_value=Build(component=Component())) 

268 @patch('CIResults.models.TestSuite.objects.get', return_value=TestSuite()) 

269 @patch('CIResults.run_import.PiglitResult', return_value=None) 

270 def test_read_results(self, piglitresult_mock, ts_mock, build_mock): 

271 ts_run = TestsuiteResults(self.runconfig, "name1", "build1", "", "piglit", 1) 

272 ts_run.read_results("machine1", "1", "path/to/results") 

273 piglitresult_mock.assert_called_once() 

274 

275 

276class TestSuiteRunDefTests(TestCase): 

277 def test_init(self): 

278 for field in ["testsuite_build", "results_format", "results_format_version_raw", 

279 "machine", "testsuite_run_id_raw", "testsuite_run_path"]: 

280 kwargs = {"testsuite_build": "BUILD", "results_format": "piglit", 

281 "results_format_version_raw": 1, "machine": "MACHINE", 

282 "testsuite_run_id_raw": 0, "testsuite_run_path": ""} 

283 kwargs[field] = None 

284 

285 msg = f"The parameter {field} cannot be None" 

286 if field in ('results_format_version_raw', 'testsuite_run_id_raw'): 

287 msg = f"The parameter {field} 'None' should be an integer" 

288 

289 self.assertRaisesMessage(ValueError, msg, TestSuiteRunDef, **kwargs) 

290 

291 

292class RunConfigResultsTests(TestCase): 

293 

294 def __mock_configparser__(self): 

295 # Replace the configparser instanciated by RunConfigResults with our own 

296 self.config = configparser.ConfigParser() 

297 patcher_api_call = patch('configparser.ConfigParser') 

298 mock_matches = patcher_api_call.start() 

299 mock_matches.return_value = self.config 

300 self.addCleanup(patcher_api_call.stop) 

301 patcher_api_call = patch('configparser.ConfigParser.read') 

302 mock_matches = patcher_api_call.start() 

303 mock_matches.return_value = None 

304 self.addCleanup(patcher_api_call.stop) 

305 

306 def test_parse_run_info__empty(self): 

307 self.__mock_configparser__() 

308 

309 # Check empty 

310 self.config.read_string("") 

311 self.assertRaisesMessage(ValueError, "The RunConfig file runconfig.ini is invalid", 

312 RunConfigResultsFromDir, "") 

313 

314 def test_parse_run_info__name_missing(self): 

315 self.__mock_configparser__() 

316 

317 # Check name missing 

318 cfg = """[CIRESULTS_RUNCONFIG] 

319 """ 

320 self.config.read_string(cfg) 

321 self.assertRaisesMessage(ValueError, "The RunConfig file runconfig.ini is invalid: runconfig name unspecified", 

322 RunConfigResultsFromDir, "") 

323 

324 @patch('pathlib.Path.walk', return_value=[]) 

325 def test_parse_run_info__minimal(self, mocked_path_walk): 

326 self.__mock_configparser__() 

327 

328 cfg = """[CIRESULTS_RUNCONFIG] 

329 name: RUNCFG1 

330 """ 

331 self.config.read_string(cfg) 

332 runcfg = RunConfigResultsFromDir("") 

333 self.assertEqual(runcfg.name, "RUNCFG1") 

334 self.assertEqual(runcfg.url, None) 

335 self.assertEqual(runcfg.environment, None) 

336 self.assertEqual(runcfg.builds, []) 

337 self.assertEqual(runcfg.tags, []) 

338 self.assertEqual(runcfg.testsuites, {}) 

339 self.assertFalse(runcfg.temporary) 

340 

341 def test_parse_run_info__invalid_build(self): 

342 self.__mock_configparser__() 

343 

344 cfg = """[CIRESULTS_RUNCONFIG] 

345 name: RUNCFG1 

346 

347 [Testsuite1] 

348 build: build1 

349 """ 

350 self.config.read_string(cfg) 

351 msg = "The build 'build1' of the testsuite 'Testsuite1' is not found in the list of builds " 

352 msg += "of the runconfig RUNCFG1" 

353 self.assertRaisesMessage(ValueError, msg, RunConfigResultsFromDir, "") 

354 

355 @patch('pathlib.Path.walk', return_value=[]) 

356 @patch('CIResults.run_import.TestsuiteResults') 

357 def test_parse_run_info__multi_complete(self, mocked_ts_run, mocked_path_walk): 

358 self.__mock_configparser__() 

359 

360 # Check a complete configuration 

361 cfg = """[CIRESULTS_RUNCONFIG] 

362 name: RUNCFG2 

363 url: http://test.url.com 

364 environment: blablabla 

365 blibliblib 

366 builds: build1 build2 build3 

367 tags: tag1 tag2 tag3 

368 temporary: true 

369 

370 [Testsuite1] 

371 build: build2 

372 format: format1 

373 result_url_pattern = pattern1 

374 

375 [Testsuite2] 

376 build: build3 

377 format: format2 

378 version: 2 

379 result_url_pattern = pattern2 

380 """ 

381 self.config.read_string(cfg) 

382 runcfg = RunConfigResultsFromDir("") 

383 self.assertEqual(runcfg.name, "RUNCFG2") 

384 self.assertEqual(runcfg.url, "http://test.url.com") 

385 self.assertEqual(runcfg.environment, "blablabla\nblibliblib") 

386 self.assertEqual(runcfg.builds, ['build1', 'build2', 'build3']) 

387 self.assertEqual(runcfg.tags, ['tag1', 'tag2', 'tag3']) 

388 self.assertEqual(set(runcfg.testsuites.keys()), 

389 set(["Testsuite1", "Testsuite2"])) 

390 self.assertTrue(runcfg.temporary) 

391 

392 self.assertEqual(len(mocked_ts_run.call_args_list), 2) 

393 mocked_ts_run.assert_any_call(runcfg, 'Testsuite1', 'build2', 'pattern1', 'format1', 1) 

394 mocked_ts_run.assert_any_call(runcfg, 'Testsuite2', 'build3', 'pattern2', 'format2', 2) 

395 

396 @patch('CIResults.run_import.logger') 

397 @patch('CIResults.run_import.TestsuiteResults') 

398 def test_load_results(self, mocked_ts_run, mocked_logger): 

399 runcfg = RunConfigResultsFromDir("CIResults/tests/results") 

400 

401 calls = [ 

402 call.warning("RunConfigResults: testsuite run ID '%s' should be an integer", "invalid_id"), 

403 call.info("Ignore the testsuite '%s' because it is not listed in the runconfig file", "UNREFERENCED") 

404 ] 

405 mocked_logger.assert_has_calls(calls, any_order=True) 

406 

407 self.assertNotIn("UNREFERENCED", runcfg.testsuites) 

408 

409 calls = [call('machine1', 0, 'CIResults/tests/results/RENDERCHECK/machine1/0'), 

410 call('machine2', 0, 'CIResults/tests/results/RENDERCHECK/machine2/0'), 

411 call('machine1', 0, 'CIResults/tests/results/IGT/machine1/0'), 

412 call('machine1', 1, 'CIResults/tests/results/IGT/machine1/1'), 

413 call('machine2', 0, 'CIResults/tests/results/IGT/machine2/0')] 

414 mocked_ts_run.return_value.read_results.assert_has_calls(calls, any_order=True) 

415 

416 def test_init__invalid_testsuite_build(self): 

417 results = [TestSuiteRunDef(testsuite_build="INVALID", results_format="piglit", 

418 results_format_version_raw=1, machine="machine", 

419 testsuite_run_id_raw=1, testsuite_run_path="")] 

420 msg = "The build named 'INVALID' does not exist" 

421 self.assertRaisesMessage(ValueError, msg, RunConfigResultsFromArgs, name="RUNCFG", 

422 builds=['INVALID'], results=results) 

423 

424 def test_init__testsuite_build_not_in_the_list_of_builds(self): 

425 results = [TestSuiteRunDef(testsuite_build="MISSING", results_format="piglit", 

426 results_format_version_raw=1, machine="machine", 

427 testsuite_run_id_raw=1, testsuite_run_path="")] 

428 msg = "The build named 'MISSING' is not part of the list of builds of the runconfig" 

429 self.assertRaisesMessage(ValueError, msg, RunConfigResultsFromArgs, name="RUNCFG", 

430 builds=["build1", "build2"], results=results) 

431 

432 @patch('CIResults.run_import.logger') 

433 @patch('sys.stderr', new_callable=StringIO) 

434 def test_init__dual_import_of_a_testsuite_run(self, mocked_stderr, mocked_logger): 

435 call_command('loaddata', 'CIResults/fixtures/RunConfigResults_commit_to_db', verbosity=0) 

436 

437 results = [TestSuiteRunDef(testsuite_build="build1", results_format="piglit", 

438 results_format_version_raw=1, machine="machine", 

439 testsuite_run_id_raw=1, 

440 testsuite_run_path="CIResults/tests/results/IGT/machine1/0/results.json.bz2"), 

441 TestSuiteRunDef(testsuite_build="build1", results_format="piglit", 

442 results_format_version_raw=1, machine="machine", 

443 testsuite_run_id_raw=1, 

444 testsuite_run_path="CIResults/tests/results/IGT/machine1/0/results.json.bz2")] 

445 

446 msg = "Try to import twice testsuite1's run ID 1 on the runconfig 'RUNCFG' for the machine 'machine'" 

447 self.assertRaisesMessage(ValueError, msg, RunConfigResultsFromArgs, name="RUNCFG", 

448 builds=["build1"], results=results) 

449 

450 @patch('pathlib.Path.walk', return_value=[]) 

451 def __create_commit_to_db_env__(self, mocked_path_walk, temporary=False): 

452 self.__mock_configparser__() 

453 

454 # Mock a configuration 

455 cfg = """[CIRESULTS_RUNCONFIG] 

456 name: RUNCFG 

457 url: http://test.url.com 

458 result_url_pattern = http://hello.world/{runconfig}/{machine}/{testsuite_build}/{run_id}/{test} 

459 environment: my environment 

460 builds: build1 build2 

461 tags: tag1 tag2 

462 temporary: {temporary} 

463 

464 [testsuite1] 

465 build: build1 

466 format: piglit 

467 

468 [testsuite2] 

469 build: build2 

470 format: piglit 

471 """.format(temporary=temporary, runconfig="{runconfig}", machine="{machine}", 

472 testsuite_build="{testsuite_build}", run_id="{run_id}", test="{test}") 

473 self.config.read_string(cfg) 

474 runcfg = RunConfigResultsFromDir("") 

475 

476 # Add results 

477 self.start_time_run = timezone.make_aware(datetime.datetime.fromtimestamp(0), 

478 timezone.get_default_timezone()) 

479 self.start_time_test = self.start_time_run + datetime.timedelta(seconds=1) 

480 for testsuite in ["testsuite1", "testsuite2"]: 

481 for i in range(1, 3): 

482 results = [] 

483 for t in range(1, 3): 

484 results.append(TestsuiteTestResult(name="test{}".format(t), 

485 status="pass" if t != 2 else "broken", 

486 command="command", 

487 stdout="stdout", 

488 stderr="stderr", 

489 dmesg="dmesg", 

490 start_time=self.start_time_test, 

491 duration=datetime.timedelta(seconds=1))) 

492 

493 tsr = TestsuiteRunResults(testsuite=runcfg.testsuites[testsuite], 

494 machine_name="machine{}".format(i), 

495 run_id=i, test_results=results, 

496 start_time=self.start_time_run, 

497 duration=datetime.timedelta(seconds=4242)) 

498 runcfg.run_results.append(tsr) 

499 

500 return runcfg 

501 

502 @patch('CIResults.run_import.logger') 

503 def test_commit_to_db_machine_public(self, mocked_logger): 

504 call_command('loaddata', 'CIResults/fixtures/RunConfigResults_commit_to_db', verbosity=0) 

505 runcfg = self.__create_commit_to_db_env__() 

506 

507 # Check the amount of objects before adding anything 

508 self.assertEqual(Machine.objects.all().count(), 1) 

509 self.assertEqual(Test.objects.all().count(), 1) 

510 self.assertEqual(TextStatus.objects.all().count(), 4) 

511 self.assertEqual(TestsuiteRun.objects.all().count(), 0) 

512 self.assertEqual(TestResult.objects.all().count(), 0) 

513 self.assertEqual(KnownFailure.objects.all().count(), 0) 

514 self.assertEqual(UnknownFailure.objects.all().count(), 0) 

515 self.assertEqual(RunFilterStatistic.objects.all().count(), 0) 

516 

517 testsuite1 = TestSuite.objects.get(name="testsuite1") 

518 testsuite2 = TestSuite.objects.get(name="testsuite2") 

519 

520 # Add the runconfig to the db 

521 ResultsCommitHandler(runcfg).commit(new_machines_public=True, new_tests_public=False) 

522 

523 # Check stdout 

524 calls = [ 

525 call.info("Adding %s missing %s", 1, "machine(s)"), 

526 call.info("Adding %s missing %s", 1, "test(s) (testsuite1)"), 

527 call.info("Adding %s missing %s", 2, "test(s) (testsuite2)"), 

528 call.info("Adding %s missing %s", 1, "status(es) (testsuite1)"), 

529 call.info("Adding %s missing %s", 1, "status(es) (testsuite2)"), 

530 call.info("Adding %s testsuite runs", 4), 

531 call.info("Adding %s test results", 8), 

532 call.info( 

533 "Found %s test failures (%s filters matched, %s failures left unmatched) in %.2f ms", 4, 0, 4, mock.ANY, 

534 ), 

535 call.info( 

536 "Found %s/%s recently-archived filters matching some unknown failures in %.2f ms", 0, 0, mock.ANY, 

537 ), 

538 ] 

539 self.assertTrue(all(c in mocked_logger.method_calls for c in calls)) 

540 

541 # Check that the machine and test got created with the correct public attribute 

542 self.assertTrue(Machine.objects.get(name="machine2").public) 

543 self.assertFalse(Test.objects.get(name="test2", testsuite=testsuite1).public) 

544 self.assertFalse(Test.objects.get(name="test1", testsuite=testsuite2).public) 

545 self.assertFalse(Test.objects.get(name="test2", testsuite=testsuite2).public) 

546 

547 # Check that the new status is in the database for both testsuites 

548 self.assertEqual(TextStatus.objects.filter(name="broken").count(), 2) 

549 

550 # Check that the right amount of objects is found in the db 

551 self.assertEqual(Machine.objects.all().count(), 2) 

552 self.assertEqual(Test.objects.all().count(), 4) 

553 self.assertEqual(TextStatus.objects.all().count(), 6) 

554 self.assertEqual(TestsuiteRun.objects.all().count(), 4) 

555 self.assertEqual(TestResult.objects.all().count(), 8) 

556 self.assertEqual(KnownFailure.objects.all().count(), 0) 

557 self.assertEqual(UnknownFailure.objects.all().count(), 4) 

558 self.assertEqual(RunFilterStatistic.objects.all().count(), 0) 

559 

560 # Check that the testsuite of the result matches the one of the run 

561 for result in TestResult.objects.all().select_related('status', 'ts_run'): 

562 self.assertEqual(result.status.testsuite_id, result.ts_run.testsuite_id) 

563 

564 @patch('CIResults.run_import.logger') 

565 def test_commit_to_db_test_public(self, mocked_logger): 

566 call_command('loaddata', 'CIResults/fixtures/RunConfigResults_commit_to_db', verbosity=0) 

567 runcfg = self.__create_commit_to_db_env__() 

568 

569 testsuite1 = TestSuite.objects.get(name="testsuite1") 

570 testsuite2 = TestSuite.objects.get(name="testsuite2") 

571 

572 ResultsCommitHandler(runcfg).commit(new_machines_public=False, new_tests_public=True) 

573 

574 # Check that the machine and test got created with the correct public attribute 

575 self.assertFalse(Machine.objects.get(name="machine2").public) 

576 self.assertTrue(Test.objects.get(name="test2", testsuite=testsuite1).public) 

577 self.assertTrue(Test.objects.get(name="test1", testsuite=testsuite2).public) 

578 self.assertTrue(Test.objects.get(name="test2", testsuite=testsuite2).public) 

579 

580 # Check that they all have the first_runconfig set correctly (new tests 

581 # got the current runconfig, and the old one still has the same value) 

582 old_runcfg = RunConfig.objects.get(name="OLD_RUNCFG") 

583 new_runcfg = RunConfig.objects.get(name="RUNCFG") 

584 for test in Test.objects.all(): 

585 runcfg = (old_runcfg if test.id == 1 else new_runcfg) 

586 self.assertEqual(test.first_runconfig, runcfg, test) 

587 

588 @patch('CIResults.run_import.logger') 

589 def test_commit_to_db_test_temporary(self, mocked_logger): 

590 call_command('loaddata', 'CIResults/fixtures/RunConfigResults_commit_to_db', verbosity=0) 

591 runcfg = self.__create_commit_to_db_env__(temporary=True) 

592 

593 ResultsCommitHandler(runcfg).commit(new_machines_public=False, new_tests_public=True) 

594 

595 # Check that they all have the first_runconfig set correctly, and the new 

596 # test get None instead 

597 old_runcfg = RunConfig.objects.get(name="OLD_RUNCFG") 

598 for test in Test.objects.all(): 

599 runcfg = (old_runcfg if test.id == 1 else None) 

600 self.assertEqual(test.first_runconfig, runcfg, test) 

601 

602 def test_commit_to_db__two_builds_of_the_same_component(self): 

603 call_command('loaddata', 'CIResults/fixtures/RunConfigResults_commit_to_db', verbosity=0) 

604 

605 component1 = Component.objects.get(name="testsuite1") 

606 Build.objects.create(name="build3", component=component1) 

607 

608 runcfg = RunConfigResults(name="RUNCFG", url="http://test.url.com", 

609 result_url_pattern="", builds=["build1", "build3"], 

610 tags=["tag1", "tag2"]) 

611 

612 msg = "ERROR: Two builds (build3 and build1) cannot be from the same component (testsuite1)" 

613 self.assertRaisesMessage(ValueError, msg, ResultsCommitHandler(runcfg).commit) 

614 

615 @patch('CIResults.run_import.logger') 

616 def test_commit_to_db__add_one_component(self, mocked_logger): 

617 call_command('loaddata', 'CIResults/fixtures/RunConfigResults_commit_to_db', verbosity=0) 

618 

619 # First, create a runconfig with two builds from two different testsuites 

620 runcfg = RunConfigResults(name="RUNCFG", url="http://test.url.com", 

621 result_url_pattern="", builds=["build1"], 

622 tags=["tag1", "tag2"]) 

623 ResultsCommitHandler(runcfg).commit() 

624 

625 # Add the build2 

626 runcfg = RunConfigResults(name="RUNCFG", builds=["build2"]) 

627 ResultsCommitHandler(runcfg).commit() 

628 

629 # Verify that the runconfig has been modified to also contain the new build 

630 runcfg_obj = RunConfig.objects.get(name="RUNCFG") 

631 self.assertEqual(set([b.id for b in runcfg_obj.builds.all()]), set([1, 2])) 

632 

633 @patch('CIResults.run_import.logger') 

634 def test_commit_to_db__try_changing_build_of_one_component(self, mocked_logger): 

635 call_command('loaddata', 'CIResults/fixtures/RunConfigResults_commit_to_db', verbosity=0) 

636 

637 testsuite1 = Component.objects.get(name="testsuite1") 

638 Build.objects.create(name="build3", component=testsuite1) 

639 

640 # First, create a runconfig with two builds from two different testsuites 

641 runcfg = RunConfigResults(name="RUNCFG", url="http://test.url.com", 

642 result_url_pattern="", builds=["build1", "build2"], 

643 tags=["tag1", "tag2"]) 

644 ResultsCommitHandler(runcfg).commit() 

645 

646 runcfg = RunConfigResults(name="RUNCFG", builds=["build3"]) 

647 msg = "ERROR: Two builds (build3 and build1) cannot be from the same component (testsuite1)" 

648 self.assertRaisesMessage(ValueError, msg, ResultsCommitHandler(runcfg).commit) 

649 

650 

651class issue_simple_stats_recomputingTests(TestCase): 

652 def setUp(self): 

653 self.filter1 = IssueFilter.objects.create(description="filter1") 

654 self.filter2 = IssueFilter.objects.create(description="filter2") 

655 self.unrelated_filter = IssueFilter.objects.create(description="unrelated filter") 

656 

657 self.issue = Issue.objects.create() 

658 self.issue.save = MagicMock() 

659 

660 IssueFilterAssociated.objects.create(filter=self.filter1, issue=self.issue) 

661 IssueFilterAssociated.objects.create(filter=self.filter2, issue=self.issue) 

662 

663 self.runconfig = RunConfig.objects.create(name='runconfig', temporary=False) 

664 

665 self.stats = dict() 

666 

667 def __check_result(self, runconfigs_covered_count=0, runconfigs_affected_count=0, 

668 last_seen_runconfig=None, save_called=False): 

669 

670 ResultsCommitHandler._issue_simple_stats_recomputing(self.issue, self.runconfig, self.stats) 

671 

672 self.assertEqual(self.issue.runconfigs_covered_count, runconfigs_covered_count) 

673 self.assertEqual(self.issue.runconfigs_affected_count, runconfigs_affected_count) 

674 self.assertEqual(self.issue.last_seen_runconfig, last_seen_runconfig) 

675 if last_seen_runconfig is not None: 

676 self.assertEqual(self.issue.last_seen, last_seen_runconfig.added_on) 

677 else: 

678 self.assertIsNone(self.issue.last_seen) 

679 self.assertEqual(self.issue.save.called, save_called) 

680 

681 def test_update_issue_stats__unrelated_stats_have_no_impact(self): 

682 self.stats[self.unrelated_filter] = (None, RunFilterStatistic(matched_count=42, covered_count=42)) 

683 self.__check_result() 

684 

685 def test_update_issue_stats__new_covered_filters(self): 

686 self.stats[self.filter1] = (None, RunFilterStatistic(runconfig=None, matched_count=0, covered_count=42)) 

687 self.stats[self.filter2] = (None, RunFilterStatistic(runconfig=None, matched_count=0, covered_count=10)) 

688 

689 self.__check_result(runconfigs_covered_count=1, save_called=True) 

690 

691 def test_update_issue_stats__new_covered_filters_but_was_already_covered(self): 

692 self.issue.runconfigs_covered_count = 1 

693 

694 self.stats[self.filter1] = (None, RunFilterStatistic(runconfig=None, matched_count=0, covered_count=42)) 

695 self.stats[self.filter2] = (RunFilterStatistic(runconfig=None, matched_count=0, covered_count=10), 

696 RunFilterStatistic(runconfig=None, matched_count=0, covered_count=23)) 

697 

698 self.__check_result(runconfigs_covered_count=1, save_called=False) 

699 

700 def test_update_issue_stats__new_matched_filters(self): 

701 self.issue.runconfigs_covered_count = 1 

702 

703 self.stats[self.filter1] = (None, RunFilterStatistic(runconfig=None, matched_count=0, covered_count=42)) 

704 self.stats[self.filter2] = (RunFilterStatistic(runconfig=None, matched_count=0, covered_count=10), 

705 RunFilterStatistic(runconfig=None, matched_count=2, covered_count=10)) 

706 

707 self.__check_result(runconfigs_covered_count=1, runconfigs_affected_count=1, 

708 last_seen_runconfig=self.runconfig, save_called=True) 

709 

710 def test_update_issue_stats__new_matched_filters_but_was_already_matched(self): 

711 self.issue.runconfigs_covered_count = 1 

712 self.issue.runconfigs_affected_count = 1 

713 

714 self.stats[self.filter1] = (None, RunFilterStatistic(runconfig=None, matched_count=20, covered_count=42)) 

715 self.stats[self.filter2] = (RunFilterStatistic(runconfig=None, matched_count=1, covered_count=10), 

716 RunFilterStatistic(runconfig=None, matched_count=2, covered_count=10)) 

717 

718 # NOTE: last_seen_runconfig is None because it is not written to when no changes happened 

719 self.__check_result(runconfigs_covered_count=1, runconfigs_affected_count=1, 

720 last_seen_runconfig=None, save_called=False)