Skip to content

Commit 0ba51e2

Browse files
authored
Process broken as failed, process unknown as skipped (#149)
* Update rfhistoricparser.py * Add tests for SuiteStats, SuiteResults, TestMetrics and DB insert; add junit flow test; fix allure JSON expectations - Add comprehensive unit tests covering SuiteStats counting (pass/skip/fail/empty), SuiteResults behavior (empty suites, full suite name handling), and TestMetrics.visit_test. - Add tests for insert_into_execution_table including full DB flow and division-by-zero case. - Add a rfhistoric_parser junit flow test to ensure process_junit_report is invoked and exit isn't called. - Adjust expected counts in process_allure_report_json test to match mocked data.
1 parent 45ec9f2 commit 0ba51e2

2 files changed

Lines changed: 212 additions & 4 deletions

File tree

robotframework_historic_parser/rfhistoricparser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,8 @@ def process_allure_report(opts):
287287

288288
total = statistics.get('total', '0')
289289
passed = statistics.get('passed', '0')
290-
failed = int(statistics.get('failed', '0')) + int(statistics.get('unknown', '0'))
291-
skipped = statistics.get('skipped', '0')
290+
failed = int(statistics.get('failed', '0')) + int(statistics.get('broken', '0'))
291+
skipped = int(statistics.get('skipped', '0')) + int(statistics.get('unknown', '0'))
292292
elapsedtime = '0' # duration data not saved in the summary.json
293293
else:
294294
print("Invalid file type. Please provide either .xml or .json file.")

test/rfhistoricparser_test.py

Lines changed: 210 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
commit_and_close_db,
1717
ExecutionResult,
1818
datetime,
19+
SuiteStats,
20+
SuiteResults,
21+
TestMetrics,
1922
)
2023
from robotframework_historic_parser.parserargs import parse_options
2124

@@ -208,6 +211,24 @@ def test_rfhistoric_parser_rf(
208211

209212
mock_print.assert_has_calls(expected_calls, any_order=True)
210213

214+
@patch("robotframework_historic_parser.rfhistoricparser.process_junit_report")
215+
@patch("os.listdir", return_value=["output.xml"])
216+
@patch("builtins.exit")
217+
def test_rfhistoric_parser_junit(
218+
self, mock_exit, mock_list, mock_process_junit_report
219+
):
220+
221+
opts = Mock()
222+
opts.report_type = "junit"
223+
opts.path = "/some/path"
224+
opts.output = "*.xml"
225+
226+
with patch("builtins.print"):
227+
rfhistoric_parser(opts)
228+
229+
mock_process_junit_report.assert_called_once_with(opts)
230+
mock_exit.assert_not_called()
231+
211232
@patch("robotframework_historic_parser.rfhistoricparser.process_allure_report")
212233
@patch("os.listdir", return_value=["output.xml"])
213234
@patch("builtins.exit")
@@ -334,12 +355,12 @@ def test_process_allure_report_json(
334355
opts.executionname,
335356
10,
336357
1,
337-
5,
358+
2,
338359
"0",
339360
0,
340361
0,
341362
0,
342-
4,
363+
7,
343364
0,
344365
opts.projectname,
345366
)
@@ -477,3 +498,190 @@ def test_remove_special_characters_with_numbers(self):
477498
def test_remove_special_characters_with_spaces(self):
478499
result = remove_special_characters("Remove these special characters!")
479500
self.assertEqual(result, "Remove these special characters")
501+
502+
503+
504+
def test_suite_stats_passed_suite(self):
505+
"""Test SuiteStats with passed status"""
506+
suite_stats = SuiteStats()
507+
508+
# Create a mock suite with PASS status
509+
mock_suite = Mock()
510+
mock_suite.tests = [Mock()] # Has tests
511+
mock_suite.status = "PASS"
512+
513+
suite_stats.start_suite(mock_suite)
514+
515+
self.assertEqual(suite_stats.total_suite, 1)
516+
self.assertEqual(suite_stats.passed_suite, 1)
517+
self.assertEqual(suite_stats.failed_suite, 0)
518+
self.assertEqual(suite_stats.skipped_suite, 0)
519+
520+
def test_suite_stats_skipped_suite(self):
521+
"""Test SuiteStats with skipped status"""
522+
suite_stats = SuiteStats()
523+
524+
# Create a mock suite with SKIP status
525+
mock_suite = Mock()
526+
mock_suite.tests = [Mock()] # Has tests
527+
mock_suite.status = "SKIP"
528+
529+
suite_stats.start_suite(mock_suite)
530+
531+
self.assertEqual(suite_stats.total_suite, 1)
532+
self.assertEqual(suite_stats.passed_suite, 0)
533+
self.assertEqual(suite_stats.failed_suite, 0)
534+
self.assertEqual(suite_stats.skipped_suite, 1)
535+
536+
def test_suite_stats_failed_suite(self):
537+
"""Test SuiteStats with failed status"""
538+
suite_stats = SuiteStats()
539+
540+
# Create a mock suite with FAIL status
541+
mock_suite = Mock()
542+
mock_suite.tests = [Mock()] # Has tests
543+
mock_suite.status = "FAIL"
544+
545+
suite_stats.start_suite(mock_suite)
546+
547+
self.assertEqual(suite_stats.total_suite, 1)
548+
self.assertEqual(suite_stats.passed_suite, 0)
549+
self.assertEqual(suite_stats.failed_suite, 1)
550+
self.assertEqual(suite_stats.skipped_suite, 0)
551+
552+
def test_suite_stats_empty_suite(self):
553+
"""Test SuiteStats with empty suite (no tests)"""
554+
suite_stats = SuiteStats()
555+
556+
# Create a mock suite with no tests
557+
mock_suite = Mock()
558+
mock_suite.tests = [] # Empty test list
559+
mock_suite.status = "PASS"
560+
561+
suite_stats.start_suite(mock_suite)
562+
563+
# Should not count empty suites
564+
self.assertEqual(suite_stats.total_suite, 0)
565+
self.assertEqual(suite_stats.passed_suite, 0)
566+
self.assertEqual(suite_stats.failed_suite, 0)
567+
self.assertEqual(suite_stats.skipped_suite, 0)
568+
569+
def test_suite_results_empty_suite(self):
570+
"""Test SuiteResults with empty suite (no tests)"""
571+
mock_db = Mock()
572+
suite_results = SuiteResults(mock_db, "123", "False")
573+
574+
# Create a mock suite with no tests
575+
mock_suite = Mock()
576+
mock_suite.tests = [] # Empty test list
577+
578+
with patch("robotframework_historic_parser.rfhistoricparser.insert_into_suite_table") as mock_insert:
579+
suite_results.start_suite(mock_suite)
580+
# Should not insert empty suites
581+
mock_insert.assert_not_called()
582+
583+
def test_suite_results_full_suite_name_true(self):
584+
"""Test SuiteResults with full_suite_name=True"""
585+
mock_db = Mock()
586+
suite_results = SuiteResults(mock_db, "123", "True")
587+
588+
# Create a mock suite with longname
589+
mock_suite = MagicMock()
590+
mock_suite.tests = [Mock()] # Has tests
591+
mock_suite.longname = "MyProject.MySuite.MySubSuite"
592+
mock_suite.status = "PASS"
593+
594+
# Create a simple object to hold statistics
595+
class Stats:
596+
total = 10
597+
passed = 5
598+
failed = 3
599+
skipped = 2
600+
601+
mock_suite.statistics = Stats()
602+
mock_suite.elapsedtime = 120000 # 2 minutes in milliseconds
603+
604+
with patch("robotframework_historic_parser.rfhistoricparser.insert_into_suite_table") as mock_insert:
605+
suite_results.start_suite(mock_suite)
606+
# Verify insert was called with the full suite name
607+
self.assertTrue(mock_insert.called)
608+
# Check that longname was used (first call, argument index 2 is the suite name)
609+
call_args = mock_insert.call_args[0]
610+
self.assertEqual(call_args[2], "MyProject.MySuite.MySubSuite")
611+
612+
def test_suite_results_visit_test_full_suite_name_true(self):
613+
"""Test TestMetrics.visit_test with full_suite_name=True"""
614+
from robotframework_historic_parser.rfhistoricparser import TestMetrics
615+
616+
mock_db = Mock()
617+
test_results = TestMetrics(mock_db, "123", "True")
618+
619+
# Create a mock test with longname
620+
mock_test = Mock()
621+
mock_test.name = "MyTest"
622+
mock_test.longname = "MyProject.MySuite.MyTest"
623+
mock_test.status = "PASS"
624+
mock_test.elapsedtime = 60000 # 1 minute in milliseconds
625+
mock_test.message = "Test passed"
626+
mock_test.tags = ["tag1", "tag2"]
627+
628+
with patch("robotframework_historic_parser.rfhistoricparser.insert_into_test_table"):
629+
test_results.visit_test(mock_test)
630+
631+
@patch("mysql.connector.connect")
632+
def test_insert_into_execution_table_full_coverage(self, mock_connect):
633+
"""Test insert_into_execution_table with all database operations"""
634+
mock_con = Mock()
635+
mock_ocon = Mock()
636+
mock_cursor = Mock()
637+
mock_root_cursor = Mock()
638+
639+
mock_con.cursor.return_value = mock_cursor
640+
mock_ocon.cursor.return_value = mock_root_cursor
641+
642+
# Mock the fetchone results
643+
mock_cursor.fetchone.side_effect = [
644+
(1, 5, 10), # Execution_Id, Execution_Pass, Execution_Total
645+
(100,) # COUNT(*)
646+
]
647+
648+
result = insert_into_execution_table(
649+
mock_con, mock_ocon, "Test Execution", 10, 5, 3, "2.5",
650+
20, 15, 3, 2, 0, "TestProject"
651+
)
652+
653+
# Verify the execution ID is returned
654+
self.assertEqual(result, "1")
655+
656+
# Verify cursor operations
657+
mock_cursor.execute.assert_called()
658+
mock_con.commit.assert_called_once()
659+
mock_root_cursor.execute.assert_called_once()
660+
mock_ocon.commit.assert_called_once()
661+
662+
@patch("mysql.connector.connect")
663+
def test_insert_into_execution_table_zero_total(self, mock_connect):
664+
"""Test insert_into_execution_table handles division by zero"""
665+
mock_con = Mock()
666+
mock_ocon = Mock()
667+
mock_cursor = Mock()
668+
mock_root_cursor = Mock()
669+
670+
mock_con.cursor.return_value = mock_cursor
671+
mock_ocon.cursor.return_value = mock_root_cursor
672+
673+
# Mock with zero total to test division by zero handling
674+
mock_cursor.fetchone.side_effect = [
675+
(1, 0, 0), # Execution_Id, Execution_Pass=0, Execution_Total=0
676+
(100,) # COUNT(*)
677+
]
678+
679+
result = insert_into_execution_table(
680+
mock_con, mock_ocon, "Test Execution", 0, 0, 0, "0",
681+
0, 0, 0, 0, 0, "TestProject"
682+
)
683+
684+
# Verify it doesn't crash and returns the execution ID
685+
self.assertEqual(result, "1")
686+
mock_con.commit.assert_called_once()
687+
mock_ocon.commit.assert_called_once()

0 commit comments

Comments
 (0)