From 26bd5e1700d2d0a89706c61c7404ffb4f80fec3e Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 24 Jul 2015 13:13:25 +0200 Subject: [PATCH] Add TimeMachineTest IT --- .../xoo-history-v1/sonar-project.properties | 5 + .../src/main/xoo/sample/ClassToModify.xoo | 12 + .../xoo/sample/ClassToModify.xoo.measures | 2 + .../src/main/xoo/sample/UnchangedClass.xoo | 12 + .../xoo/sample/UnchangedClass.xoo.measures | 2 + .../xoo-history-v2/sonar-project.properties | 5 + .../src/main/xoo/sample/ClassAdded.xoo | 12 + .../main/xoo/sample/ClassAdded.xoo.measures | 3 + .../src/main/xoo/sample/ClassToModify.xoo | 16 ++ .../xoo/sample/ClassToModify.xoo.measures | 3 + .../src/main/xoo/sample/UnchangedClass.xoo | 12 + .../xoo/sample/UnchangedClass.xoo.measures | 2 + .../measure/suite/MeasureFiltersTest.java | 40 +-- .../java/measure/suite/TimeMachineTest.java | 161 +++++++++++ .../one-issue-per-line-profile.xml | 12 + .../measure_filters/copy_measure_filter.html | 134 ++++++++++ .../copy_uniqueness_of_name.html | 139 ++++++++++ .../suite/measure_filters/empty_filter.html | 30 +++ .../measure_filters/initial_search_form.html | 34 +++ .../link_from_main_header.html | 40 +++ .../measure_filters/list_change_columns.html | 84 ++++++ .../measure_filters/list_delete_column.html | 74 ++++++ .../measure_filters/list_move_columns.html | 74 ++++++ .../list_sort_by_descending_name.html | 70 +++++ .../measure_filters/list_sort_by_ncloc.html | 94 +++++++ .../suite/measure_filters/list_widget.html | 90 +++++++ .../measure_filters/list_widget_sort.html | 144 ++++++++++ ...list_widget_warning_if_missing_filter.html | 24 ++ .../save_with_special_characters.html | 121 +++++++++ .../suite/measure_filters/search-by-key.html | 69 +++++ .../suite/measure_filters/search-by-name.html | 69 +++++ .../measure_filters/search_for_files.html | 49 ++++ .../measure_filters/search_for_projects.html | 64 +++++ ...when-user-have-no-sharing-permissions.html | 94 +++++++ ...filter-remove-other-filters-favourite.html | 249 ++++++++++++++++++ 35 files changed, 2025 insertions(+), 20 deletions(-) create mode 100644 it/it-projects/measure/xoo-history-v1/sonar-project.properties create mode 100644 it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo create mode 100644 it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo.measures create mode 100644 it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo create mode 100644 it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo.measures create mode 100644 it/it-projects/measure/xoo-history-v2/sonar-project.properties create mode 100644 it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo create mode 100644 it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo.measures create mode 100644 it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo create mode 100644 it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo.measures create mode 100644 it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo create mode 100644 it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo.measures create mode 100644 it/it-tests/src/test/java/measure/suite/TimeMachineTest.java create mode 100644 it/it-tests/src/test/resources/measure/suite/TimeMachineTest/one-issue-per-line-profile.xml create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/copy_measure_filter.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/copy_uniqueness_of_name.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/empty_filter.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/initial_search_form.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/link_from_main_header.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_change_columns.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_delete_column.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_move_columns.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_descending_name.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_ncloc.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_sort.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_warning_if_missing_filter.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/save_with_special_characters.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-key.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-name.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_files.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_projects.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/should-not-share-filter-when-user-have-no-sharing-permissions.html create mode 100644 it/it-tests/src/test/resources/measure/suite/measure_filters/should-unshare-filter-remove-other-filters-favourite.html diff --git a/it/it-projects/measure/xoo-history-v1/sonar-project.properties b/it/it-projects/measure/xoo-history-v1/sonar-project.properties new file mode 100644 index 00000000000..e01f062e51b --- /dev/null +++ b/it/it-projects/measure/xoo-history-v1/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.projectKey=sample +sonar.projectName=Sample +sonar.projectVersion=1.0-SNAPSHOT +sonar.sources=src/main/xoo +sonar.language=xoo \ No newline at end of file diff --git a/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo new file mode 100644 index 00000000000..2b0288fc971 --- /dev/null +++ b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo @@ -0,0 +1,12 @@ +package sample; + +public class ClassToModify { + + public ClassToModify(int i) { + int j = i++; + } + + private String myMethod() { + return "hello"; + } +} diff --git a/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo.measures b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo.measures new file mode 100644 index 00000000000..7812e4167fb --- /dev/null +++ b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/ClassToModify.xoo.measures @@ -0,0 +1,2 @@ +ncloc:12 +classes:1 diff --git a/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo new file mode 100644 index 00000000000..2b0288fc971 --- /dev/null +++ b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo @@ -0,0 +1,12 @@ +package sample; + +public class ClassToModify { + + public ClassToModify(int i) { + int j = i++; + } + + private String myMethod() { + return "hello"; + } +} diff --git a/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo.measures b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo.measures new file mode 100644 index 00000000000..7812e4167fb --- /dev/null +++ b/it/it-projects/measure/xoo-history-v1/src/main/xoo/sample/UnchangedClass.xoo.measures @@ -0,0 +1,2 @@ +ncloc:12 +classes:1 diff --git a/it/it-projects/measure/xoo-history-v2/sonar-project.properties b/it/it-projects/measure/xoo-history-v2/sonar-project.properties new file mode 100644 index 00000000000..e01f062e51b --- /dev/null +++ b/it/it-projects/measure/xoo-history-v2/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.projectKey=sample +sonar.projectName=Sample +sonar.projectVersion=1.0-SNAPSHOT +sonar.sources=src/main/xoo +sonar.language=xoo \ No newline at end of file diff --git a/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo new file mode 100644 index 00000000000..b0fd1087030 --- /dev/null +++ b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo @@ -0,0 +1,12 @@ +package sample; + +public class ClassAdded { + + public ClassAdded(int i) { + int j = i++; + } + + private String myMethod() { + return "hello"; + } +} diff --git a/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo.measures b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo.measures new file mode 100644 index 00000000000..66ba834e1ef --- /dev/null +++ b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassAdded.xoo.measures @@ -0,0 +1,3 @@ +ncloc:12 +classes:1 + diff --git a/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo new file mode 100644 index 00000000000..393111bbab0 --- /dev/null +++ b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo @@ -0,0 +1,16 @@ +package sample; + +public class ClassToModify { + + public ClassToModify(int i) { + int j = i++; + } + + public String addedMethod() { + return "This method was added in v2"; + } + + private String myMethod() { + return "hello"; + } +} diff --git a/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo.measures b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo.measures new file mode 100644 index 00000000000..71d60758637 --- /dev/null +++ b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/ClassToModify.xoo.measures @@ -0,0 +1,3 @@ +ncloc:16 +classes:1 + diff --git a/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo new file mode 100644 index 00000000000..2b0288fc971 --- /dev/null +++ b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo @@ -0,0 +1,12 @@ +package sample; + +public class ClassToModify { + + public ClassToModify(int i) { + int j = i++; + } + + private String myMethod() { + return "hello"; + } +} diff --git a/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo.measures b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo.measures new file mode 100644 index 00000000000..7812e4167fb --- /dev/null +++ b/it/it-projects/measure/xoo-history-v2/src/main/xoo/sample/UnchangedClass.xoo.measures @@ -0,0 +1,2 @@ +ncloc:12 +classes:1 diff --git a/it/it-tests/src/test/java/measure/suite/MeasureFiltersTest.java b/it/it-tests/src/test/java/measure/suite/MeasureFiltersTest.java index 4d6c3a68148..1162d612c99 100644 --- a/it/it-tests/src/test/java/measure/suite/MeasureFiltersTest.java +++ b/it/it-tests/src/test/java/measure/suite/MeasureFiltersTest.java @@ -53,14 +53,14 @@ public class MeasureFiltersTest { @Test public void execute_measure_filters() { Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("execution_of_measure_filters", - "/measure/measure_filters/link_from_main_header.html", - "/measure/measure_filters/initial_search_form.html", - "/measure/measure_filters/search_for_projects.html", - "/measure/measure_filters/search_for_files.html", + "/measure/suite/measure_filters/link_from_main_header.html", + "/measure/suite/measure_filters/initial_search_form.html", + "/measure/suite/measure_filters/search_for_projects.html", + "/measure/suite/measure_filters/search_for_files.html", // SONAR-4195 - "/measure/measure_filters/search-by-key.html", - "/measure/measure_filters/search-by-name.html", - "/measure/measure_filters/empty_filter.html" + "/measure/suite/measure_filters/search-by-key.html", + "/measure/suite/measure_filters/search-by-name.html", + "/measure/suite/measure_filters/empty_filter.html" ).build(); orchestrator.executeSelenese(selenese); } @@ -68,11 +68,11 @@ public class MeasureFiltersTest { @Test public void display_measure_filter_as_list() { Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("display_measure_filter_as_list", - "/measure/measure_filters/list_change_columns.html", - "/measure/measure_filters/list_delete_column.html", - "/measure/measure_filters/list_move_columns.html", - "/measure/measure_filters/list_sort_by_descending_name.html", - "/measure/measure_filters/list_sort_by_ncloc.html" + "/measure/suite/measure_filters/list_change_columns.html", + "/measure/suite/measure_filters/list_delete_column.html", + "/measure/suite/measure_filters/list_move_columns.html", + "/measure/suite/measure_filters/list_sort_by_descending_name.html", + "/measure/suite/measure_filters/list_sort_by_ncloc.html" ).build(); orchestrator.executeSelenese(selenese); } @@ -86,7 +86,7 @@ public class MeasureFiltersTest { try { Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("share_measure_filters", // SONAR-4469 - "/measure/measure_filters/should-unshare-filter-remove-other-filters-favourite.html" + "/measure/suite/measure_filters/should-unshare-filter-remove-other-filters-favourite.html" ).build(); orchestrator.executeSelenese(selenese); @@ -105,7 +105,7 @@ public class MeasureFiltersTest { try { orchestrator.executeSelenese(Selenese.builder().setHtmlTestsInClasspath("should_not_share_filter_when_user_have_no_sharing_permissions", - "/measure/measure_filters/should-not-share-filter-when-user-have-no-sharing-permissions.html" + "/measure/suite/measure_filters/should-not-share-filter-when-user-have-no-sharing-permissions.html" ).build()); } finally { deactivateUser(user); @@ -115,8 +115,8 @@ public class MeasureFiltersTest { @Test public void copy_measure_filters() { Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("copy_measure_filters", - "/measure/measure_filters/copy_measure_filter.html", - "/measure/measure_filters/copy_uniqueness_of_name.html" + "/measure/suite/measure_filters/copy_measure_filter.html", + "/measure/suite/measure_filters/copy_uniqueness_of_name.html" ).build(); orchestrator.executeSelenese(selenese); } @@ -124,7 +124,7 @@ public class MeasureFiltersTest { @Test public void manage_measure_filters() { Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("manage_measure_filters", - "/measure/measure_filters/save_with_special_characters.html" + "/measure/suite/measure_filters/save_with_special_characters.html" ).build(); orchestrator.executeSelenese(selenese); } @@ -132,9 +132,9 @@ public class MeasureFiltersTest { @Test public void measure_filter_list_widget() { Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("measure_filter_list_widget", - "/measure/measure_filters/list_widget.html", - "/measure/measure_filters/list_widget_sort.html", - "/measure/measure_filters/list_widget_warning_if_missing_filter.html" + "/measure/suite/measure_filters/list_widget.html", + "/measure/suite/measure_filters/list_widget_sort.html", + "/measure/suite/measure_filters/list_widget_warning_if_missing_filter.html" ).build(); orchestrator.executeSelenese(selenese); } diff --git a/it/it-tests/src/test/java/measure/suite/TimeMachineTest.java b/it/it-tests/src/test/java/measure/suite/TimeMachineTest.java new file mode 100644 index 00000000000..6db9cb99fd7 --- /dev/null +++ b/it/it-tests/src/test/java/measure/suite/TimeMachineTest.java @@ -0,0 +1,161 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package measure.suite; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarRunner; +import com.sonar.orchestrator.locator.FileLocation; +import java.util.Date; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.services.Resource; +import org.sonar.wsclient.services.ResourceQuery; +import org.sonar.wsclient.services.TimeMachine; +import org.sonar.wsclient.services.TimeMachineCell; +import org.sonar.wsclient.services.TimeMachineQuery; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class TimeMachineTest { + + private static final String PROJECT = "sample"; + + @ClassRule + public static Orchestrator orchestrator = MeasuresTestSuite.ORCHESTRATOR; + + @BeforeClass + public static void initialize() { + orchestrator.resetData(); + orchestrator.getServer().restoreProfile(FileLocation.ofClasspath("/measure/suite/TimeMachineTest/one-issue-per-line-profile.xml")); + orchestrator.getServer().provisionProject("sample", "Sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + analyzeProject("measure/xoo-history-v1", "2014-10-19"); + analyzeProject("measure/xoo-history-v2", "2014-11-13"); + } + + private static BuildResult analyzeProject(String path, String date) { + return orchestrator.executeBuild(SonarRunner.create(projectDir(path), "sonar.projectDate", date)); + } + + @Test + public void projectIsAnalyzed() { + assertThat(orchestrator.getServer().getWsClient().find(new ResourceQuery(PROJECT)).getVersion()).isEqualTo("1.0-SNAPSHOT"); + assertThat(orchestrator.getServer().getWsClient().find(new ResourceQuery(PROJECT)).getDate().getMonth()).isEqualTo(10); // November + } + + @Test + public void testHistoryOfIssues() { + TimeMachineQuery query = TimeMachineQuery.createForMetrics(PROJECT, "blocker_violations", "critical_violations", "major_violations", + "minor_violations", "info_violations"); + TimeMachine timemachine = orchestrator.getServer().getWsClient().find(query); + assertThat(timemachine.getCells().length).isEqualTo(2); + + TimeMachineCell cell1 = timemachine.getCells()[0]; + TimeMachineCell cell2 = timemachine.getCells()[1]; + + assertThat(cell1.getDate().getMonth()).isEqualTo(9); + assertThat(cell1.getValues()).isEqualTo(new Object[] {0L, 0L, 0L, 26L, 0L}); + + assertThat(cell2.getDate().getMonth()).isEqualTo(10); + assertThat(cell2.getValues()).isEqualTo(new Object[] {0L, 0L, 0L, 43L, 0L}); + } + + @Test + public void testHistoryOfMeasures() { + TimeMachineQuery query = TimeMachineQuery.createForMetrics(PROJECT, "lines", "ncloc"); + TimeMachine timemachine = orchestrator.getServer().getWsClient().find(query); + assertThat(timemachine.getCells().length).isEqualTo(2); + + TimeMachineCell cell1 = timemachine.getCells()[0]; + TimeMachineCell cell2 = timemachine.getCells()[1]; + + assertThat(cell1.getDate().getMonth()).isEqualTo(9); + assertThat(cell1.getValues()).isEqualTo(new Object[] {26L, 24L}); + + assertThat(cell2.getDate().getMonth()).isEqualTo(10); + assertThat(cell2.getValues()).isEqualTo(new Object[] {43L, 40L}); + } + + @Test + public void unknownMetrics() { + TimeMachine timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT, "notfound")); + assertThat(timemachine.getCells().length).isEqualTo(0); + + timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT, "lines", "notfound")); + assertThat(timemachine.getCells().length).isEqualTo(2); + for (TimeMachineCell cell : timemachine.getCells()) { + assertThat(cell.getValues().length).isEqualTo(1); + assertThat(cell.getValues()[0]).isInstanceOf(Long.class); + } + + timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT)); + assertThat(timemachine.getCells().length).isEqualTo(0); + } + + @Test + public void noDataForInterval() { + Date now = new Date(); + TimeMachine timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT, "lines").setFrom(now).setTo(now)); + assertThat(timemachine.getCells().length).isEqualTo(0); + } + + @Test + public void unknownResource() { + TimeMachine timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics("notfound:notfound", "lines")); + assertThat(timemachine).isNull(); + } + + @Test + public void test_measure_variations() { + Resource project = getProject("files", "ncloc", "violations"); + + // period 1 : previous analysis + assertThat(project.getPeriod1Mode()).isEqualTo("previous_analysis"); + assertThat(project.getPeriod1Date()).isNotNull(); + + // variations from previous analysis + assertThat(project.getMeasure("files").getVariation1()).isEqualTo(1.0); + assertThat(project.getMeasure("ncloc").getVariation1()).isEqualTo(16.0); + assertThat(project.getMeasure("violations").getVariation1()).isGreaterThan(0.0); + } + + /** + * SONAR-4962 + */ + @Test + public void measure_variations_are_only_meaningful_when_includetrends() { + String[] metricKeys = {"violations", "new_violations"}; + + Resource projectWithTrends = orchestrator.getServer().getWsClient().find(ResourceQuery.createForMetrics(PROJECT, metricKeys).setIncludeTrends(true)); + assertThat(projectWithTrends.getMeasure("violations")).isNotNull(); + assertThat(projectWithTrends.getMeasure("new_violations")).isNotNull(); + + Resource projectWithoutTrends = orchestrator.getServer().getWsClient().find(ResourceQuery.createForMetrics(PROJECT, metricKeys).setIncludeTrends(false)); + assertThat(projectWithoutTrends.getMeasure("violations")).isNotNull(); + assertThat(projectWithoutTrends.getMeasure("new_violations")).isNull(); + } + + private Resource getProject(String... metricKeys) { + return orchestrator.getServer().getWsClient().find(ResourceQuery.createForMetrics(PROJECT, metricKeys).setIncludeTrends(true)); + } +} diff --git a/it/it-tests/src/test/resources/measure/suite/TimeMachineTest/one-issue-per-line-profile.xml b/it/it-tests/src/test/resources/measure/suite/TimeMachineTest/one-issue-per-line-profile.xml new file mode 100644 index 00000000000..521adc7e06f --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/TimeMachineTest/one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ + + + one-issue-per-line + xoo + + + xoo + OneIssuePerLine + MINOR + + + \ No newline at end of file diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/copy_measure_filter.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/copy_measure_filter.html new file mode 100644 index 00000000000..22005c9a9d6 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/copy_measure_filter.html @@ -0,0 +1,134 @@ + + + + + + copy_measure_filter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storeEval'list_'+(new Date()).getTime()FILTER_NAME
open/sonar/sessions/logout
open/sonar/sessions/new
typeid=loginadmin
typeid=passwordadmin
clickAndWaitname=commit
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
clickid=save-as
waitForElementPresentsave-as-filter-form
typeid=name${FILTER_NAME}
clickid=save-as-submit
waitForTextfilter-title*${FILTER_NAME}*
clickid=copy
waitForElementPresentcopy-filter-form
typeid=namecopy of ${FILTER_NAME}
typeid=descriptiondescription of copy
clickAndWaitid=copy-submit
waitForTextfilter-title*copy of ${FILTER_NAME}*
waitForTextfilter-description*description of copy*
waitForElementPresentname=qualifiers[]Files
assertSelectedLabelname=qualifiers[]Files
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/copy_uniqueness_of_name.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/copy_uniqueness_of_name.html new file mode 100644 index 00000000000..7f6f914a8a2 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/copy_uniqueness_of_name.html @@ -0,0 +1,139 @@ + + + + + + copy_uniqueness_of_name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storejavascript{'list_'+(new Date()).getTime()}FILTER_NAME
open/sonar/sessions/logout
open/sonar/sessions/new
typeid=loginadmin
typeid=passwordadmin
clickAndWaitname=commit
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
clickid=save-as
waitForElementPresentsave-as-filter-form
typeid=name${FILTER_NAME}
clickid=save-as-submit
waitForTextfilter-title*${FILTER_NAME}*
clickid=copy
waitForElementPresentcopy-filter-form
typeid=name${FILTER_NAME}
clickid=copy-submit
waitForTextcopy-filter-form*Name already exists*
typeid=namecopy of ${FILTER_NAME}
clickid=copy-submit
waitForTextfilter-title*copy of ${FILTER_NAME}*
waitForElementPresentname=qualifiers[]Files
assertSelectedLabelname=qualifiers[]Files
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/empty_filter.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/empty_filter.html new file mode 100644 index 00000000000..65ebe3575ad --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/empty_filter.html @@ -0,0 +1,30 @@ + + + + + + empty_filter + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures/search
assertTextmeasures-table*No data*
assertNotTextmeasures-table*Struts*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/initial_search_form.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/initial_search_form.html new file mode 100644 index 00000000000..863df6c057a --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/initial_search_form.html @@ -0,0 +1,34 @@ + + + + + + initial_search_form + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
waitForTextclass=navigator-filters*More Criteria*
assertElementNotPresentmeasures-table
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/link_from_main_header.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/link_from_main_header.html new file mode 100644 index 00000000000..c92986d4489 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/link_from_main_header.html @@ -0,0 +1,40 @@ + + + + + + link_from_main_header + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/
waitForElementPresentlink=Measures
clickAndWaitlink=Measures
assertLocation*/measures*
assertElementPresentclass=navigator-filters
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_change_columns.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_change_columns.html new file mode 100644 index 00000000000..b23e78ca1c4 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_change_columns.html @@ -0,0 +1,84 @@ + + + + + + configure-table-columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
clickAndWaitid=change-display
selectselect-metricComplexity
clickAndWaitid=add-metric
assertTextmeasures-table*Name*Cmpx*
selectselect-metricComment lines
selectid=select-periodlabel=since previous analysis
clickAndWaitid=add-metric
assertTextmeasures-table*Name*Cmpx*Comment lines*last*
clickAndWaitid=exit-edit
assertTextmeasures-table*Name*Cmpx*Comment lines*last*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_delete_column.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_delete_column.html new file mode 100644 index 00000000000..3075d43b47e --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_delete_column.html @@ -0,0 +1,74 @@ + + + + + + delete_column + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
clickAndWaitid=change-display
assertTextmeasures-table*Name*Last Analysis*LOC*
clickdelete-date
waitForTextmeasures-table*Name*LOC*
assertNotTextmeasures-table*Last Analysis*
clickAndWaitid=exit-edit
assertTextmeasures-table*Name*LOC*
assertNotTextmeasures-table*Last Analysis*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_move_columns.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_move_columns.html new file mode 100644 index 00000000000..ee78b261c94 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_move_columns.html @@ -0,0 +1,74 @@ + + + + + + move_columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
clickAndWaitid=change-display
assertTextmeasures-table*Name*Last Analysis*LOC*
clickleft-date
waitForTextmeasures-table*Last Analysis*Name*LOC*
clickright-name
waitForTextmeasures-table*Last Analysis*LOC*Name*
clickAndWaitid=exit-edit
assertTextmeasures-table*Last Analysis*LOC*Name*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_descending_name.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_descending_name.html new file mode 100644 index 00000000000..8705a7efee2 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_descending_name.html @@ -0,0 +1,70 @@ + + + + + + + + list_sort_by_descending_name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
assertElementPresentmeasures-table
clickAndWaitlink=Name
assertTextmeasures-table*b2/HelloB2.xoo*b1/HelloB1.xoo*a2/HelloA2.xoo*a1/HelloA1.xoo*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_ncloc.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_ncloc.html new file mode 100644 index 00000000000..95ef3b0deeb --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_sort_by_ncloc.html @@ -0,0 +1,94 @@ + + + + + + + + list_sort_by_ncloc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
assertElementPresentmeasures-table
assertTextcss=#measures-table tfoot*4 results*
clickAndWaitlink=LOC
assertTextmeasures-table*a2/HelloA2.xoo*a1/HelloA1.xoo*b1/HelloB1.xoo*b2/HelloB2.xoo*
assertTextcss=#measures-table tfoot*4 results*
clickAndWaitlink=LOC
assertTextcss=#measures-table tfoot*4 results*
assertTextmeasures-table*a1/HelloA1.xoo*b1/HelloB1.xoo*b2/HelloB2.xoo*a2/HelloA2.xoo*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget.html new file mode 100644 index 00000000000..81695821706 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget.html @@ -0,0 +1,90 @@ + + + + + + + + list_widget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/sessions/logout
open/sonar/measures/manage
typeid=loginadmin
typeid=passwordadmin
clickAndWaitname=commit
clickAndWaitxpath=(//table[contains(@id, 'shared-filters')]//a[contains(text(),'Projects')])
storeEvalwindow.location.pathname.split('/').last()FILTER_ID
open/sonar/widget?id=measure_filter_list&filter=${FILTER_ID}
assertTextblock_1*Name*Version*LOC*Last Analysis*
assertTextblock_1*Multi-modules Sample*1.0-SNAPSHOT*
assertNotTextblock_1*Module A*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_sort.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_sort.html new file mode 100644 index 00000000000..82b5f7e6eea --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_sort.html @@ -0,0 +1,144 @@ + + + + + + + + list_widget_sort + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storeEval'sort_widget_'+(new Date()).getTime()FILTER_NAME
open/sonar/sessions/logout
open/sonar/sessions/new
typeloginadmin
typepasswordadmin
clickAndWaitname=commit
open/sonar/measures
waitForTextclass=navigator-filters*More Criteria*
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
clickid=save-as
waitForElementPresentsave-as-filter-form
typename${FILTER_NAME}
clickAndWaitid=save-as-submit
storeEvalwindow.location.pathname.split('/').last()FILTER_ID
open/sonar/widget?id=measure_filter_list&filter=${FILTER_ID}
assertTextblock_1*Name*LOC*
assertTextblock_1*a1/HelloA1.xoo*a2/HelloA2.xoo*b1/HelloB1.xoo*b2/HelloB2.xoo*
clicklink=LOC
waitForTextblock_1*a2/HelloA2.xoo*a1/HelloA1.xoo*b1/HelloB1.xoo*b2/HelloB2.xoo*
clicklink=LOC
waitForTextblock_1*HelloA1*HelloB1*HelloB2*HelloA2*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_warning_if_missing_filter.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_warning_if_missing_filter.html new file mode 100644 index 00000000000..4bcd30c0ba4 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/list_widget_warning_if_missing_filter.html @@ -0,0 +1,24 @@ + + + + + + list_widget + + + + + + + + + + + + + + + +
open/sonar/widget?id=measure_filter_list
assertTextblock_1*This widget is configured to display a measure filter that does not exist anymore.*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/save_with_special_characters.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/save_with_special_characters.html new file mode 100644 index 00000000000..19b116782e9 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/save_with_special_characters.html @@ -0,0 +1,121 @@ + + + + + + save_with_special_characters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/sessions/logout
open/sonar/sessions/new
typeloginadmin
typepasswordadmin
clickAndWaitcommit
open/sonar/measures
waitForElementPresentname=qualifiers[]
clickAndWaitclass=navigator-filter-submit
clickid=save-as
waitForElementPresentsave-as-filter-form
typeid=namespecial $àé'@"
typeid=descriptiondescription with special characters " ' éà
clickAndWaitid=save-as-submit
assertTextfilter-description*description with special characters " ' éà*
waitForElementPresentid=manage-favorites
clickAndWaitid=manage-favorites
assertTextmy-filters*special $àé'@"*description with special characters " ' éà*
clickid=delete_special-ae
waitForElementPresentconfirm-submit
clickid=confirm-submit
waitForNotTextmy-filters*special $àé'@"*
+ + + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-key.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-key.html new file mode 100644 index 00000000000..50fce19e151 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-key.html @@ -0,0 +1,69 @@ + + + + + + + + search-by-key + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Projects
typename=keySearchmulti-modules
clickAndWaitclass=navigator-filter-submit
assertTextmeasures-table*Sonar :: Integration Tests :: Multi-modules Sample*
assertTextcss=#measures-table tfoot*1 results*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-name.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-name.html new file mode 100644 index 00000000000..2c4fb8c53d3 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/search-by-name.html @@ -0,0 +1,69 @@ + + + + + + + + search-by-name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Projects
typename=nameSearchIntegration
clickAndWaitclass=navigator-filter-submit
assertTextmeasures-table*Sonar :: Integration Tests :: Multi-modules Sample*
assertTextcss=#measures-table tfoot*1 results*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_files.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_files.html new file mode 100644 index 00000000000..4e001a597c9 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_files.html @@ -0,0 +1,49 @@ + + + + + + search_for_files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Files
clickAndWaitclass=navigator-filter-submit
assertTextmeasures-table*a1/HelloA1.xoo*a2/HelloA2.xoo*b1/HelloB1.xoo*b2/HelloB2.xoo*
assertTextcss=#measures-table tfoot*4 results*
assertNotTextmeasures-table*Module A*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_projects.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_projects.html new file mode 100644 index 00000000000..c7c0e83bdb2 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/search_for_projects.html @@ -0,0 +1,64 @@ + + + + + + + + search_for_projects_by_default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Projects
clickAndWaitclass=navigator-filter-submit
assertElementPresentmeasures-table
assertTextmeasures-table*Sonar :: Integration Tests :: Multi-modules Sample*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/should-not-share-filter-when-user-have-no-sharing-permissions.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/should-not-share-filter-when-user-have-no-sharing-permissions.html new file mode 100644 index 00000000000..94f85a0e3c5 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/should-not-share-filter-when-user-have-no-sharing-permissions.html @@ -0,0 +1,94 @@ + + + + + + should_save_issue_filters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storeEval'filter_'+(new Date()).getTime()FILTER_NAME
open/sonar/sessions/logout
open/sonar/sessions/new
typeloginuser-measures-filter-with-no-share-perm
typepasswordpassword
clickAndWaitcommit
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Projects
clickAndWaitclass=navigator-filter-submit
clickid=save-as
waitForTextsave-as-filter-form*Save Filter*
typeid=nameuser_${FILTER_NAME}
assertNotTextsave-as-filter-form*Shared with all users*
clickid=save-as-submit
waitForTextfilter-title*user_${FILTER_NAME}*Private*
+ + diff --git a/it/it-tests/src/test/resources/measure/suite/measure_filters/should-unshare-filter-remove-other-filters-favourite.html b/it/it-tests/src/test/resources/measure/suite/measure_filters/should-unshare-filter-remove-other-filters-favourite.html new file mode 100644 index 00000000000..8b6fb44c4e5 --- /dev/null +++ b/it/it-tests/src/test/resources/measure/suite/measure_filters/should-unshare-filter-remove-other-filters-favourite.html @@ -0,0 +1,249 @@ + + + + + + should_save_issue_filters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storeEval'filter_'+(new Date()).getTime()FILTER_NAME
open/sonar/sessions/logout
open/sonar/sessions/new
typeloginuser-measures-filter-with-sharing-perm
typepasswordpassword
clickAndWaitcommit
open/sonar/measures
waitForElementPresentname=qualifiers[]
selectname=qualifiers[]Projects
clickAndWaitclass=navigator-filter-submit
clickid=save-as
waitForTextsave-as-filter-form*Save Filter*
typeid=nameuser_${FILTER_NAME}
clickid=shared
clickid=save-as-submit
waitForTextfilter-title*user_${FILTER_NAME}*Shared with all users*
open/sonar/sessions/logout
open/sonar/sessions/new
typeloginadmin
typepasswordadmin
clickAndWaitcommit
open/sonar/measures/manage
waitForTextcontent*My Measures Filters*
assertTextshared-filters*user_${FILTER_NAME}*
clickid=star-user_${FILTER_NAME}
open/sonar/sessions/logout
open/sonar/sessions/new
typeloginuser-measures-filter-with-sharing-perm
typepasswordpassword
clickAndWaitcommit
open/sonar/measures
waitForElementPresentid=manage-favorites
clickAndWaitid=manage-favorites
waitForTextmy-filters*user_${FILTER_NAME}*
clickid=edit_user_${FILTER_NAME}
waitForTextedit-filter-form*Edit Filter*
clickshared
clickAndWaitid=save-submit
waitForTextfilter-title*user_${FILTER_NAME}*Private*
open/sonar/sessions/logout
open/sonar/sessions/new
typeloginadmin
typepasswordadmin
clickAndWaitcommit
open/sonar/measures/manage
waitForTextcontent*My Measures Filters*
assertNotTextshared-filters*user_${FILTER_NAME}*
+ + -- 2.39.5