From baf13acba44e32f9f6198105e283181f55e48948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Csord=C3=A1s?= Date: Thu, 29 Oct 2020 13:38:57 +0100 Subject: [PATCH] [web] Introduce a default other component Introduce a default auto-generated other component that contains files that are uncovered by the rest of the components. --- .../codechecker_server/api/report_server.py | 105 ++++++++++---- .../Filters/SourceComponentFilter.vue | 2 +- .../SourceComponent/ListSourceComponents.vue | 3 +- .../SourceComponentTooltip.vue | 8 +- .../Statistics/ComponentStatistics.vue | 4 +- .../functional/component/test_component.py | 135 ++++++++++++++++-- 6 files changed, 213 insertions(+), 44 deletions(-) diff --git a/web/server/codechecker_server/api/report_server.py b/web/server/codechecker_server/api/report_server.py index 66ab4b27ba..f41f6083cd 100644 --- a/web/server/codechecker_server/api/report_server.py +++ b/web/server/codechecker_server/api/report_server.py @@ -64,6 +64,7 @@ LOG = get_logger('server') +GEN_OTHER_COMPONENT_NAME = "Other (auto-generated)" class CommentKindValue(object): USER = 0 @@ -317,32 +318,8 @@ def process_report_filter(session, run_ids, report_filter, cmp_data=None): AND.append(or_(*OR)) if report_filter.componentNames: - OR = [] - - for component_name in report_filter.componentNames: - skip, include = get_component_values(session, component_name) - - if skip and include: - include_q = select([File.id]) \ - .where(or_(*[ - File.filepath.like(conv(fp)) for fp in include])) \ - .distinct() - - skip_q = select([File.id]) \ - .where(or_(*[ - File.filepath.like(conv(fp)) for fp in skip])) \ - .distinct() - - OR.append(or_(File.id.in_( - include_q.except_(skip_q)))) - elif include: - include_q = [File.filepath.like(conv(fp)) for fp in include] - OR.append(or_(*include_q)) - elif skip: - skip_q = [not_(File.filepath.like(conv(fp))) for fp in skip] - OR.append(and_(*skip_q)) - - AND.append(or_(*OR)) + AND.append(process_source_component_filter( + session, report_filter.componentNames)) if report_filter.bugPathLength is not None: min_path_length = report_filter.bugPathLength.min @@ -357,6 +334,67 @@ def process_report_filter(session, run_ids, report_filter, cmp_data=None): return filter_expr +def process_source_component_filter(session, component_names): + """ Process source component filter. + + The virtual auto-generated Other component will be handled separately and + the query part will be added to the filter. + """ + OR = [] + + for component_name in component_names: + if component_name == GEN_OTHER_COMPONENT_NAME: + OR.append(or_(*get_other_source_component_file_query(session))) + continue + + OR.append(or_(*get_source_component_file_query(session, + component_name))) + + return or_(*OR) + + +def get_source_component_file_query(session, component_name): + """ Get filter query for a single source component. """ + skip, include = get_component_values(session, component_name) + + if skip and include: + include_q = select([File.id]) \ + .where(or_(*[ + File.filepath.like(conv(fp)) for fp in include])) \ + .distinct() + + skip_q = select([File.id]) \ + .where(or_(*[ + File.filepath.like(conv(fp)) for fp in skip])) \ + .distinct() + + return [File.id.in_(include_q.except_(skip_q))] + elif include: + return [File.filepath.like(conv(fp)) for fp in include] + elif skip: + return [not_(File.filepath.like(conv(fp))) for fp in skip] + + +def get_other_source_component_file_query(session): + """ Get filter query for the auto-generated Others component. """ + OR = [] + + component_names = session.query(SourceComponent.name).all() + + # If there are no user defined source components we don't have to filter. + if not component_names: + return [] + + for (component_name, ) in component_names: + OR.extend(get_source_component_file_query(session, component_name)) + + q = select([File.id]) \ + .where(or_(*OR)) \ + .distinct() + + return [File.id.notin_(q)] + + def get_open_reports_date_filter_query(tbl=Report, date=RunHistory.time): """ Get open reports date filter. """ return and_(tbl.detected_at <= date, @@ -2379,9 +2417,18 @@ def getSourceComponents(self, component_filter): q = q.order_by(SourceComponent.name) - return list([SourceComponentData(c.name, - c.value.decode('utf-8'), - c.description) for c in q]) + components = [SourceComponentData(c.name, c.value.decode('utf-8'), + c.description) for c in q] + + component_other = \ + SourceComponentData(GEN_OTHER_COMPONENT_NAME, None, + "Special auto-generated source component " + "which contains files that are uncovered " + "by the rest of the components.") + + components.append(component_other) + + return components @exc_to_thrift_reqfail @timeit diff --git a/web/server/vue-cli/src/components/Report/ReportFilter/Filters/SourceComponentFilter.vue b/web/server/vue-cli/src/components/Report/ReportFilter/Filters/SourceComponentFilter.vue index b65d5ee14f..9b47e8281c 100644 --- a/web/server/vue-cli/src/components/Report/ReportFilter/Filters/SourceComponentFilter.vue +++ b/web/server/vue-cli/src/components/Report/ReportFilter/Filters/SourceComponentFilter.vue @@ -111,7 +111,7 @@ export default { return { id : component.name, title: component.name, - value: component.value + value: component.value || component.description }; })); this.loading = false; diff --git a/web/server/vue-cli/src/components/Report/SourceComponent/ListSourceComponents.vue b/web/server/vue-cli/src/components/Report/SourceComponent/ListSourceComponents.vue index 8760712658..e3456a1a2a 100644 --- a/web/server/vue-cli/src/components/Report/SourceComponent/ListSourceComponents.vue +++ b/web/server/vue-cli/src/components/Report/SourceComponent/ListSourceComponents.vue @@ -150,7 +150,8 @@ export default { this.loading = true; ccService.getClient().getSourceComponents(null, handleThriftError(components => { - this.components = components; + this.components = components.filter(c => + !c.name.includes("auto-generated")); this.loading = false; })); }, diff --git a/web/server/vue-cli/src/components/Report/SourceComponent/SourceComponentTooltip.vue b/web/server/vue-cli/src/components/Report/SourceComponent/SourceComponentTooltip.vue index 778022b8e6..abab4a35ae 100644 --- a/web/server/vue-cli/src/components/Report/SourceComponent/SourceComponentTooltip.vue +++ b/web/server/vue-cli/src/components/Report/SourceComponent/SourceComponentTooltip.vue @@ -15,7 +15,9 @@ {{ v }} @@ -49,6 +51,10 @@ export default { &.exclude { color: red !important; } + + &.other { + color: black !important; + } } } diff --git a/web/server/vue-cli/src/components/Statistics/ComponentStatistics.vue b/web/server/vue-cli/src/components/Statistics/ComponentStatistics.vue index 419a6098c8..5129145913 100644 --- a/web/server/vue-cli/src/components/Statistics/ComponentStatistics.vue +++ b/web/server/vue-cli/src/components/Statistics/ComponentStatistics.vue @@ -149,7 +149,7 @@ export default { return { component : component.name, - value : component.value, + value : component.value || component.description, reports : initDiffField(res[0]), unreviewed : initDiffField(res[1]), confirmed : initDiffField(res[2]), @@ -166,7 +166,7 @@ export default { this.statistics = this.components.map(component => ({ component : component.name, - value : component.value, + value : component.value || component.description, reports : initDiffField(undefined), unreviewed : initDiffField(undefined), confirmed : initDiffField(undefined), diff --git a/web/tests/functional/component/test_component.py b/web/tests/functional/component/test_component.py index e191b7d566..b22f176ceb 100644 --- a/web/tests/functional/component/test_component.py +++ b/web/tests/functional/component/test_component.py @@ -20,6 +20,8 @@ from libtest import env +GEN_OTHER_COMPONENT_NAME = "Other (auto-generated)" + class TestComponent(unittest.TestCase): @@ -129,19 +131,61 @@ def __remove_source_component(self, name): ret = self._cc_client.removeSourceComponent(name) self.assertTrue(ret) + def __get_user_defined_source_components(self): + """ Get user defined source components. """ + components = self._cc_client.getSourceComponents(None) + self.assertNotEqual(len(components), 0) + + return [c for c in components + if GEN_OTHER_COMPONENT_NAME not in c.name] + + def __test_other_component(self, components, excluded_from_other, + included_in_other=None): + """ + Test that the results filtered by the given components and the Other + components covers all the reports and the results filtered by the + Other component doesn't contain reports which are covered by rest of + the component. + """ + all_results = self._cc_client.getRunResults(None, 500, 0, None, None, + None, False) + + r_filter = ReportFilter(componentNames=[c['name'] for c in components]) + component_results = self._cc_client.getRunResults(None, 500, 0, None, + r_filter, None, + False) + self.assertNotEqual(len(component_results), 0) + + r_filter = ReportFilter(componentNames=[GEN_OTHER_COMPONENT_NAME]) + other_results = self._cc_client.getRunResults(None, 500, 0, None, + r_filter, None, False) + self.assertNotEqual(len(other_results), 0) + + self.assertEqual(len(all_results), + len(component_results) + len(other_results)) + + for f_path in excluded_from_other: + self.assertEqual(len([r for r in other_results if + r.checkedFile.endswith(f_path)]), 0) + + if included_in_other: + for f_path in included_in_other: + self.assertNotEqual(len([r for r in other_results if + r.checkedFile.endswith(f_path)]), 0) + def test_component_management(self): """ Test management of the components. """ test_component = self.components[0] - # There are no source components available. - components = self._cc_client.getSourceComponents(None) + # There are no user defined source components available. + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 0) self.__add_new_component(test_component) - components = self._cc_client.getSourceComponents(None) + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 1) self.assertEqual(components[0].name, test_component['name']) self.assertEqual(components[0].value, test_component['value']) @@ -150,8 +194,8 @@ def test_component_management(self): self.__remove_source_component(test_component['name']) - # There are no source components available. - components = self._cc_client.getSourceComponents(None) + # There are no user defined source components available. + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 0) def test_filter_report_by_component(self): @@ -160,7 +204,7 @@ def test_filter_report_by_component(self): """ test_component = self.components[0] - components = self._cc_client.getSourceComponents(None) + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 0) self.__add_new_component(test_component) @@ -203,7 +247,7 @@ def test_filter_report_by_complex_component(self): """ test_component = self.components[2] - components = self._cc_client.getSourceComponents(None) + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 0) self.__add_new_component(test_component) @@ -253,13 +297,13 @@ def test_filter_report_by_multiple_components(self): test_component1 = self.components[3] test_component2 = self.components[4] - components = self._cc_client.getSourceComponents(None) + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 0) self.__add_new_component(test_component1) self.__add_new_component(test_component2) - components = self._cc_client.getSourceComponents(None) + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 2) r_filter = ReportFilter(componentNames=[test_component1['name'], @@ -303,7 +347,7 @@ def test_filter_report_by_excluding_all_results_component(self): """ test_component = self.components[5] - components = self._cc_client.getSourceComponents(None) + components = self.__get_user_defined_source_components() self.assertEqual(len(components), 0) self.__add_new_component(test_component) @@ -332,3 +376,74 @@ def test_component_name_with_whitespaces(self): self.__add_new_component(test_component) self.__remove_source_component(test_component['name']) + + def test_no_user_defined_component(self): + """ + When there is no user defined component in the database, the + auto-generated Other component will not filter the results. + """ + components = self.__get_user_defined_source_components() + self.assertEqual(len(components), 0) + + all_results = self._cc_client.getRunResults(None, 500, 0, None, None, + None, False) + self.assertIsNotNone(all_results) + + r_filter = ReportFilter(componentNames=[GEN_OTHER_COMPONENT_NAME]) + other_results = self._cc_client.getRunResults(None, 500, 0, None, + r_filter, None, False) + + self.assertEqual(len(all_results), len(other_results)) + + def test_other_with_single_user_defined_component(self): + """ + Test that the Other component will not contain reports which are + covered by a single component. + """ + component = { + 'name': 'UserDefined', + 'value': '\n'.join(['+*/divide_zero.cpp', + '+*/new_delete.cpp'])} + + self.__add_new_component(component) + + excluded_from_other = ['divide_zero.cpp', 'new_delete.cpp'] + self.__test_other_component([component], excluded_from_other) + self.__remove_source_component(component['name']) + + def test_other_with_multiple_user_defined_component(self): + """ + Test that the Other component will not contain reports which are + covered by multiple components. + """ + components = [ + { + 'name': 'UserDefined1', + 'value': '+*/divide_zero.cpp' + }, + { + 'name': 'UserDefined2', + 'value': '+*/new_delete.cpp' + }, + { + 'name': 'UserDefined3', + 'value': '\n'.join([ + '+*/null_dereference.cpp', + '-*/call_and_message.cpp', + ]) + } + ] + + for c in components: + self.__add_new_component(c) + + excluded_from_other = ['divide_zero.cpp', 'new_delete.cpp', + 'null_dereference.cpp'] + + included_in_other = [ 'call_and_message.cpp' ] + + self.__test_other_component(components, excluded_from_other, + included_in_other) + + for c in components: + self.__remove_source_component(c['name'])