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
« 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
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
17import configparser
18import datetime
21class BuildResultTests(TestCase):
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)
31 # Make the read() function a noop
32 self.config.read = lambda x: 0
34 def test_parse_run_info__empty(self):
35 self.__mock_configparser__()
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, "")
43 def test_parse_run_info__more_sections(self):
44 self.__mock_configparser__()
46 cfg = """[CIRESULTS_BUILD]
47 field = value
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, "")
55 def test_parse_run_info__minimal(self):
56 self.__mock_configparser__()
58 component = Component.objects.create(name="COMPONENT1", description="desc",
59 url="url", public=True)
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()
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)
81 @patch('builtins.open', mock_open(read_data="Parameters\nfile"))
82 def test_parse_run_info__parameters_file(self):
83 self.__mock_configparser__()
85 Component.objects.create(name="COMPONENT1", description="desc", url="url", public=True)
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()
96 obj = Build.objects.get(name="BUILD1")
97 self.assertEqual(obj.parameters, "Parameters\nfile")
99 @patch('builtins.open', mock_open(read_data="Parameters\nfile"))
100 def test_parse_run_info__parameters_priority(self):
101 self.__mock_configparser__()
103 Component.objects.create(name="COMPONENT1", description="desc", url="url", public=True)
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()
115 obj = Build.objects.get(name="BUILD1")
116 self.assertEqual(obj.parameters, "parameters inline")
118 @patch('builtins.open', mock_open(read_data="My\nBuild\nLog"))
119 def test_parse_run_info__complete(self):
120 self.__mock_configparser__()
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)
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()
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")
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)
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()
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")
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)
205 # Check a complete configuration
206 build = BuildResult.from_dict(name="BUILD2", component="COMPONENT1", version="VERSION1", parents=[])
207 build.commit_to_db()
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())
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"
222 self.assertEqual(TestsuiteRunResults.get_results_url(testsuite, 42, "machine1", "test1"),
223 "http://hello.world/runcfg/machine1/build1/42/test1")
226class TestsuiteResultsTests(TestCase):
227 def setUp(self):
228 self.runconfig = Mock(name="runcfg")
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)
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)
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)
264 self.assertRaisesMessage(ValueError, "The version 0 of the testsuite result format 'piglit' is unsupported",
265 TestsuiteResults, self.runconfig, "name1", "build1", "", "piglit", 0)
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()
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
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"
289 self.assertRaisesMessage(ValueError, msg, TestSuiteRunDef, **kwargs)
292class RunConfigResultsTests(TestCase):
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)
306 def test_parse_run_info__empty(self):
307 self.__mock_configparser__()
309 # Check empty
310 self.config.read_string("")
311 self.assertRaisesMessage(ValueError, "The RunConfig file runconfig.ini is invalid",
312 RunConfigResultsFromDir, "")
314 def test_parse_run_info__name_missing(self):
315 self.__mock_configparser__()
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, "")
324 @patch('pathlib.Path.walk', return_value=[])
325 def test_parse_run_info__minimal(self, mocked_path_walk):
326 self.__mock_configparser__()
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)
341 def test_parse_run_info__invalid_build(self):
342 self.__mock_configparser__()
344 cfg = """[CIRESULTS_RUNCONFIG]
345 name: RUNCFG1
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, "")
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__()
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
370 [Testsuite1]
371 build: build2
372 format: format1
373 result_url_pattern = pattern1
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)
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)
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")
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)
407 self.assertNotIn("UNREFERENCED", runcfg.testsuites)
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)
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)
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)
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)
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")]
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)
450 @patch('pathlib.Path.walk', return_value=[])
451 def __create_commit_to_db_env__(self, mocked_path_walk, temporary=False):
452 self.__mock_configparser__()
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}
464 [testsuite1]
465 build: build1
466 format: piglit
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("")
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)))
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)
500 return runcfg
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__()
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)
517 testsuite1 = TestSuite.objects.get(name="testsuite1")
518 testsuite2 = TestSuite.objects.get(name="testsuite2")
520 # Add the runconfig to the db
521 ResultsCommitHandler(runcfg).commit(new_machines_public=True, new_tests_public=False)
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))
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)
547 # Check that the new status is in the database for both testsuites
548 self.assertEqual(TextStatus.objects.filter(name="broken").count(), 2)
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)
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)
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__()
569 testsuite1 = TestSuite.objects.get(name="testsuite1")
570 testsuite2 = TestSuite.objects.get(name="testsuite2")
572 ResultsCommitHandler(runcfg).commit(new_machines_public=False, new_tests_public=True)
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)
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)
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)
593 ResultsCommitHandler(runcfg).commit(new_machines_public=False, new_tests_public=True)
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)
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)
605 component1 = Component.objects.get(name="testsuite1")
606 Build.objects.create(name="build3", component=component1)
608 runcfg = RunConfigResults(name="RUNCFG", url="http://test.url.com",
609 result_url_pattern="", builds=["build1", "build3"],
610 tags=["tag1", "tag2"])
612 msg = "ERROR: Two builds (build3 and build1) cannot be from the same component (testsuite1)"
613 self.assertRaisesMessage(ValueError, msg, ResultsCommitHandler(runcfg).commit)
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)
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()
625 # Add the build2
626 runcfg = RunConfigResults(name="RUNCFG", builds=["build2"])
627 ResultsCommitHandler(runcfg).commit()
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]))
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)
637 testsuite1 = Component.objects.get(name="testsuite1")
638 Build.objects.create(name="build3", component=testsuite1)
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()
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)
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")
657 self.issue = Issue.objects.create()
658 self.issue.save = MagicMock()
660 IssueFilterAssociated.objects.create(filter=self.filter1, issue=self.issue)
661 IssueFilterAssociated.objects.create(filter=self.filter2, issue=self.issue)
663 self.runconfig = RunConfig.objects.create(name='runconfig', temporary=False)
665 self.stats = dict()
667 def __check_result(self, runconfigs_covered_count=0, runconfigs_affected_count=0,
668 last_seen_runconfig=None, save_called=False):
670 ResultsCommitHandler._issue_simple_stats_recomputing(self.issue, self.runconfig, self.stats)
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)
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()
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))
689 self.__check_result(runconfigs_covered_count=1, save_called=True)
691 def test_update_issue_stats__new_covered_filters_but_was_already_covered(self):
692 self.issue.runconfigs_covered_count = 1
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))
698 self.__check_result(runconfigs_covered_count=1, save_called=False)
700 def test_update_issue_stats__new_matched_filters(self):
701 self.issue.runconfigs_covered_count = 1
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))
707 self.__check_result(runconfigs_covered_count=1, runconfigs_affected_count=1,
708 last_seen_runconfig=self.runconfig, save_called=True)
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
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))
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)