Skip to content

Commit

Permalink
SLE-969: Create integration test with CDT
Browse files Browse the repository at this point in the history
Using simple Makefile-based project for testing the C analysis and configuration in the sub-module.

Additionally, updated the CI components to provide all tools for using gcc/g++/make with Eclipse CDT.

The CI image now needs libwebkit2gtk-4.* as the underlying rendering component of the browser instances used in the SonarQube Rule Description view. Due to Eclipse 4.8 (the oldest target platform) requiring GTK3 - this is not available on the Linux machines on the CI - the RuleDescriptionViewTest was disabled on that axis!
  • Loading branch information
thahnen committed Nov 25, 2024
1 parent 64de97a commit 64c654c
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 6 deletions.
6 changes: 5 additions & 1 deletion .cirrus/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2
./aws/install && \
rm awscliv2.zip

# Node.js for JS/TS/CSS analysis
# "build-essential" (containing gcc/g++/make) for C/C++ analysis
# Webkit libraries for rendering the SonarQube Rule Description view
ARG NODE_VERSION=20
RUN apt-get update && apt-get install -y metacity xvfb ffmpeg nodejs=${NODE_VERSION}.* gettext-base \
RUN apt-get update && apt-get install -y build-essential metacity xvfb ffmpeg nodejs=${NODE_VERSION}.* gettext-base \
libwebkit2gtk-4.* \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY --chmod=755 .cirrus/init.d/xvfb /etc/init.d/
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ target/
# Eclipse
.classpath
.project
.cproject
.settings

# IntelliJ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
-->
<features>
<feature id="org.eclipse.platform"/>
<feature id="org.eclipse.cdt"/>
<feature id="org.eclipse.jdt"/>
<feature id="org.eclipse.m2e.feature"/>
<feature id="org.sonarlint.eclipse.feature"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@
import org.assertj.core.groups.Tuple;
import org.eclipse.reddeer.common.wait.TimePeriod;
import org.eclipse.reddeer.common.wait.WaitUntil;
import org.eclipse.reddeer.common.wait.WaitWhile;
import org.eclipse.reddeer.eclipse.ui.perspectives.JavaPerspective;
import org.eclipse.reddeer.swt.impl.link.DefaultLink;
import org.eclipse.reddeer.swt.impl.shell.DefaultShell;
import org.eclipse.reddeer.workbench.core.condition.JobIsRunning;
import org.eclipse.reddeer.workbench.impl.editor.DefaultEditor;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.sonarlint.eclipse.its.shared.AbstractSonarLintTest;
import org.sonarlint.eclipse.its.shared.reddeer.conditions.CFamilyLoaded;
import org.sonarlint.eclipse.its.shared.reddeer.conditions.ConfirmConnectionCreationDialogOpened;
import org.sonarlint.eclipse.its.shared.reddeer.conditions.DialogMessageIsExpected;
import org.sonarlint.eclipse.its.shared.reddeer.conditions.FileNotFoundDialogOpened;
Expand All @@ -51,11 +57,15 @@
import org.sonarlint.eclipse.its.shared.reddeer.dialogs.FixSuggestionAvailableDialog;
import org.sonarlint.eclipse.its.shared.reddeer.dialogs.FixSuggestionUnavailableDialog;
import org.sonarlint.eclipse.its.shared.reddeer.dialogs.ProjectSelectionDialog;
import org.sonarlint.eclipse.its.shared.reddeer.perspectives.CppPerspective;
import org.sonarlint.eclipse.its.shared.reddeer.views.BindingsView;
import org.sonarlint.eclipse.its.shared.reddeer.views.OnTheFlyView;
import org.sonarlint.eclipse.its.shared.reddeer.views.SonarLintConsole;
import org.sonarlint.eclipse.its.shared.reddeer.wizards.ProjectBindingWizard;
import org.sonarlint.eclipse.its.shared.reddeer.wizards.ServerConnectionWizard;
import org.sonarqube.ws.ProjectBranches.Branch;
import org.sonarqube.ws.client.HttpConnector;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.WsClientFactories;
import org.sonarqube.ws.client.projectbranches.ListRequest;
Expand All @@ -64,6 +74,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.tuple;

public class SonarCloudConnectedModeTest extends AbstractSonarLintTest {
private static final String TIMESTAMP = Long.toString(Instant.now().toEpochMilli());
Expand All @@ -72,9 +83,11 @@ public class SonarCloudConnectedModeTest extends AbstractSonarLintTest {
private static final String SONARCLOUD_USER = "sonarlint-it";
private static final String SONARCLOUD_PASSWORD = System.getenv("SONARCLOUD_IT_PASSWORD");
private static final String TOKEN_NAME = "SLE-IT-" + TIMESTAMP;
private static final String CONNECTION_NAME = "connection";
private static final String SAMPLE_JAVA_ISSUES_PROJECT_KEY = "sonarlint-its-sample-java-issues";
private static final String MAKEFILE_PROJECT_KEY = "MakefileProject";
private static final String MAKEFILE_PROJECT_SONAR_KEY = MAKEFILE_PROJECT_KEY + "-" + TIMESTAMP;

private static HttpConnector connector;
private static WsClient adminWsClient;
private static String token;
private static String firstSonarCloudProjectKey;
Expand All @@ -83,10 +96,13 @@ public class SonarCloudConnectedModeTest extends AbstractSonarLintTest {

@BeforeClass
public static void prepare() {
adminWsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
connector = HttpConnector.newBuilder()
.url(SONARCLOUD_STAGING_URL)
.credentials(SONARCLOUD_USER, SONARCLOUD_PASSWORD)
.build());
.build();
adminWsClient = WsClientFactories.getDefault().newClient(connector);

createProject(MAKEFILE_PROJECT_KEY, MAKEFILE_PROJECT_SONAR_KEY);

token = adminWsClient.userTokens()
.generate(new GenerateRequest().setName(TOKEN_NAME))
Expand All @@ -108,6 +124,9 @@ public static void prepare() {
public static void cleanupOrchestrator() {
adminWsClient.userTokens()
.revoke(new RevokeRequest().setName(TOKEN_NAME));

// Because we only use CDT in here, we switch back for other tests to not get confused!
new JavaPerspective().open();
}

@Before
Expand All @@ -117,6 +136,34 @@ public void cleanBindings() {
bindingsView.removeAllBindings();
}

@Test
public void test_makefile_based_project() {
// i) Open C/C++ perspective and import project
new CppPerspective().open();
var rootProject = importExistingProjectIntoWorkspace("cdt/MakefileProject", MAKEFILE_PROJECT_KEY);

// ii) Open file and await notification and no SonarLint issue to be shown
openFileAndWaitForAnalysisCompletion(rootProject.getResource("hello.c"));
shellByName("SonarQube for Eclipse - Language could not be analyzed").ifPresent(DefaultShell::close);

var onTheFlyView = new OnTheFlyView();
onTheFlyView.open();
waitForNoSonarLintMarkers(onTheFlyView);
new DefaultEditor().close();

// iii) Create connection / bind project and SonarLint issue to be shown
createConnectionAndBindProject(MAKEFILE_PROJECT_KEY, MAKEFILE_PROJECT_SONAR_KEY);
shellByName("SonarQube - Binding Suggestion").ifPresent(DefaultShell::close);
new SonarLintConsole().clear();

openFileAndWaitForAnalysisCompletion(rootProject.getResource("hello.c"));
new WaitUntil(new CFamilyLoaded(new SonarLintConsole().getConsoleView()), TimePeriod.getCustom(120));
onTheFlyView = new OnTheFlyView();
onTheFlyView.open();
waitForSonarLintMarkers(onTheFlyView,
tuple("Complete the task associated to this \"TODO\" comment.", "hello.c", "few seconds ago"));
}

@Test
public void configureServerWithTokenAndOrganization() throws InterruptedException {
var wizard = new ServerConnectionWizard();
Expand Down Expand Up @@ -150,14 +197,14 @@ public void configureServerWithTokenAndOrganization() throws InterruptedExceptio

var connectionNamePage = new ServerConnectionWizard.ConnectionNamePage(wizard);
assertThat(connectionNamePage.getConnectionName()).isEqualTo(SONARCLOUD_ORGANIZATION_KEY);
connectionNamePage.setConnectionName(CONNECTION_NAME);
assertThat(wizard.isNextEnabled()).isTrue();
connectionNamePage.setConnectionName(SONARCLOUD_ORGANIZATION_KEY);

// Sadly we have to invoke sleep here as in the background there is SL communicating with SC regarding the
// availability of notifications "on the server". As this is not done in a job we could listen to, we wait the
// 5 seconds. Once we change it in SonarLint to not ask for notifications (for all supported SQ versions and SC
// they are supported by now), we can somehow circumvent this.
Thread.sleep(5000);
assertThat(wizard.isNextEnabled()).isTrue();
wizard.next();

var notificationsPage = new ServerConnectionWizard.NotificationsPage(wizard);
Expand Down Expand Up @@ -439,4 +486,92 @@ private void triggerOpenFixSuggestion(String projectKey, String issueKey, String
var response = HttpClient.newHttpClient().send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
assertThat(response.statusCode()).isEqualTo(200);
}

/**
* Create the connection and bind a project where the project key used on SonarCloud staging differs as it is
* generated for every build.
*
* @param projectKey equals project name
* @param sonarProjectKey generated project key
*/
protected static void createConnectionAndBindProject(String projectKey, String sonarProjectKey) {
var wizard = new ServerConnectionWizard();
wizard.open();
new ServerConnectionWizard.ServerTypePage(wizard).selectSonarCloud();
wizard.next();

assertThat(wizard.isNextEnabled()).isFalse();
var authenticationPage = new ServerConnectionWizard.AuthenticationPage(wizard);
authenticationPage.setToken(token);
assertThat(wizard.isNextEnabled()).isTrue();
wizard.next();

var organizationsPage = new ServerConnectionWizard.OrganizationsPage(wizard);
organizationsPage.waitForOrganizationsToBeFetched();

assertThat(organizationsPage.getOrganization()).isEqualTo(SONARCLOUD_ORGANIZATION_KEY);

organizationsPage.setOrganization(SONARCLOUD_ORGANIZATION_KEY);
assertThat(wizard.isNextEnabled()).isTrue();
wizard.next();

var connectionNamePage = new ServerConnectionWizard.ConnectionNamePage(wizard);
assertThat(connectionNamePage.getConnectionName()).isEqualTo(SONARCLOUD_ORGANIZATION_KEY);
connectionNamePage.setConnectionName(SONARCLOUD_ORGANIZATION_KEY);

// Sadly we have to invoke sleep here as in the background there is SL communicating with SC regarding the
// availability of notifications "on the server". As this is not done in a job we could listen to, we wait the
// 5 seconds. Once we change it in SonarLint to not ask for notifications (for all supported SQ versions and SC
// they are supported by now), we can somehow circumvent this.
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
assertThat(wizard.isNextEnabled()).isTrue();
wizard.next();

var notificationsPage = new ServerConnectionWizard.NotificationsPage(wizard);
assertThat(notificationsPage.areNotificationsEnabled()).isTrue();
assertThat(wizard.isNextEnabled()).isTrue();
wizard.next();

assertThat(wizard.isNextEnabled()).isFalse();
// Because of the binding background job that is triggered we have to wait here for the project binding wizard to
// appear. It might happen that the new wizards opens over the old one before it closes, but this is okay as the
// old one will close itself lazily.
wizard.finish(TimePeriod.VERY_LONG);
new WaitWhile(new JobIsRunning(), TimePeriod.LONG);
new WaitUntil(new ProjectBindingWizardIsOpened());

var projectBindingWizard = new ProjectBindingWizard();
var projectsToBindPage = new ProjectBindingWizard.BoundProjectsPage(projectBindingWizard);

// Because RedDeer can be faster than the actual UI, we have to wait for the page to populate itself!
try {
Thread.sleep(500);
} catch (Exception ignored) {
}
projectsToBindPage.clickAdd();

var projectSelectionDialog = new ProjectSelectionDialog();
projectSelectionDialog.filterProjectName(projectKey);
projectSelectionDialog.ok();

projectBindingWizard.next();
var serverProjectSelectionPage = new ProjectBindingWizard.ServerProjectSelectionPage(projectBindingWizard);
serverProjectSelectionPage.waitForProjectsToBeFetched();
serverProjectSelectionPage.setProjectKey(sonarProjectKey);
projectBindingWizard.finish();
}

/** Creating a project on SonarCloud with all necessary information */
private static void createProject(String projectName, String projectKey) {
assertThat(hotspotServerPort).isNotEqualTo(-1);

var response = connector.call(new PostRequest("/api/projects/create")
.setParam("name", projectName)
.setParam("project", projectKey)
.setParam("organization", SONARCLOUD_ORGANIZATION_KEY));
assertThat(response.code()).isEqualTo(200);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.sonarlint.eclipse.its.shared.reddeer.conditions;

import org.eclipse.reddeer.common.condition.AbstractWaitCondition;
import org.eclipse.reddeer.eclipse.ui.console.ConsoleView;

/**
* When analyzing after a connection was established, make sure that everything was downloaded correctly and
* specifically await the CFamily analyzer to be available (for tests with Eclipse CDT).
*/
public class CFamilyLoaded extends AbstractWaitCondition {
private static final String PATTERN = "CFamily Code Quality and Security";

private final ConsoleView consoleView;

public CFamilyLoaded(ConsoleView consoleView) {
this.consoleView = consoleView;
}

@Override
public boolean test() {
var consoleText = consoleView.getConsoleText();
return consoleText.lastIndexOf(PATTERN) != -1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SonarLint for Eclipse ITs
* Copyright (C) 2009-2024 SonarSource SA
* [email protected]
*
* This program 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.
*
* This program 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 org.sonarlint.eclipse.its.shared.reddeer.perspectives;

import org.eclipse.reddeer.eclipse.ui.perspectives.AbstractPerspective;

/** The perspective used for C/C++ development */
public class CppPerspective extends AbstractPerspective {
public CppPerspective() {
super("C/C++");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.reddeer.eclipse.ui.perspectives.JavaPerspective;
import org.eclipse.reddeer.workbench.impl.editor.DefaultEditor;
import org.hamcrest.CoreMatchers;
import org.junit.Assume;
import org.junit.Test;
import org.sonarlint.eclipse.its.shared.AbstractSonarLintTest;
import org.sonarlint.eclipse.its.shared.reddeer.conditions.RuleDescriptionViewIsLoaded;
Expand All @@ -39,6 +40,9 @@ public class RuleDescriptionViewTest extends AbstractSonarLintTest {

@Test
public void openRuleDescription() {
// Because the CI can only provide GTK 4+ WebKit libraries, Eclipse 4.8 requires GTK3 tho!
Assume.assumeTrue(!"oldest-java-11_e48".equals(System.getProperty("target.platform")));

new JavaPerspective().open();
var ruleDescriptionView = new RuleDescriptionView();
ruleDescriptionView.open();
Expand All @@ -61,6 +65,9 @@ public void openRuleDescription() {

@Test
public void openRuleDescription_with_educational_content() {
// Because the CI can only provide GTK 4+ WebKit libraries, Eclipse 4.8 requires GTK3 tho!
Assume.assumeTrue(!"oldest-java-11_e48".equals(System.getProperty("target.platform")));

var ruleConfigurationPreferences = RuleConfigurationPreferences.open();
var monsterClassRule = ruleConfigurationPreferences.selectRule("java:S6539", "Java", "Classes should not depend on an excessive number of classes (aka Monster Class)");
ruleConfigurationPreferences.setRuleParameter(2);
Expand Down Expand Up @@ -97,6 +104,9 @@ public void openRuleDescription_with_educational_content() {
*/
@Test
public void openRuleRescription_with_PythonSyntaxHighlighting() {
// Because the CI can only provide GTK 4+ WebKit libraries, Eclipse 4.8 requires GTK3 tho!
Assume.assumeTrue(!"oldest-java-11_e48".equals(System.getProperty("target.platform")));

new JavaPerspective().open();
var ruleDescriptionView = new RuleDescriptionView();
ruleDescriptionView.open();
Expand Down
42 changes: 42 additions & 0 deletions its/projects/cdt/MakefileProject/.cproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="cdt.managedbuild.toolchain.gnu.base.511974735">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.511974735" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration buildProperties="" id="cdt.managedbuild.toolchain.gnu.base.511974735" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="cdt.managedbuild.toolchain.gnu.base.511974735.677895738" name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.base.148910555" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.base">
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.GNU_ELF" id="cdt.managedbuild.target.gnu.platform.base.1948530916" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/>
<builder id="cdt.managedbuild.target.gnu.builder.base.983945785" managedBuildOn="false" name="Gnu Make Builder.Default" superClass="cdt.managedbuild.target.gnu.builder.base"/>
<tool id="cdt.managedbuild.tool.gnu.archiver.base.2137577005" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.1296772266" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base"/>
<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1084654063" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base"/>
<tool id="cdt.managedbuild.tool.gnu.c.linker.base.1549098579" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.1050671083" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base"/>
<tool id="cdt.managedbuild.tool.gnu.assembler.base.2030162087" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base"/>
</toolChain>
</folderInfo>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="MakefileProject.null.1868164048" name="MakefileProject"/>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
</cproject>
Loading

0 comments on commit 64c654c

Please sign in to comment.