ext {
release = project.hasProperty('release') && project.getProperty('release')
official = project.hasProperty('official') && project.getProperty('official')
+ withTestMonitoring = project.hasProperty('withTestMonitoring')
}
ext.enableBom = enableBom
tasks.withType(Test) {
configurations {
utMonitoring
+ testMonitoring
}
dependencies {
testImplementation project(":ut-monitoring")
+ testImplementation project(":test-monitoring")
utMonitoring 'org.aspectj:aspectjweaver:1.9.19'
+ testMonitoring 'org.aspectj:aspectjweaver:1.9.19'
}
}
}
}
+ if (ext.withTestMonitoring) {
+ tasks.withType(Test) {
+ doFirst {
+ ext {
+ aspectJWeaver = configurations.testMonitoring.resolvedConfiguration.resolvedArtifacts.find { it.name == 'aspectjweaver' }
+ }
+ jvmArgs "-javaagent:${aspectJWeaver.file}"
+ }
+ }
+ }
+
signing {
def signingKeyId = findProperty("signingKeyId")
include 'sonar-testing-ldap'
include 'sonar-ws'
include 'sonar-ws-generator'
+include 'test-monitoring'
include 'ut-monitoring'
ext.isCiServer = System.getenv().containsKey("CIRRUS_CI")
--- /dev/null
+sonar {
+ properties {
+ property 'sonar.projectName', "${projectTitle} :: Java Tests Monitoring"
+ }
+}
+
+dependencies {
+ // please keep the list grouped by configuration and ordered by name
+
+ api 'com.google.code.gson:gson'
+ api 'junit:junit'
+
+ compileOnlyApi 'org.aspectj:aspectjtools'
+ compileOnlyApi 'com.google.code.findbugs:jsr305'
+
+ testImplementation 'org.assertj:assertj-core'
+ testImplementation 'org.mockito:mockito-core'
+}
+
+tasks.withType(JavaCompile) {
+ options.release = 11
+}
+
+sonar {
+ skipProject = true
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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.sonarqube.monitoring.test;
+
+public class Measure {
+ private String timestamp;
+ private String branchName;
+ private String commit;
+ private String build;
+ private String category;
+ private String testClass;
+ private String testMethod;
+ private String exceptionClass;
+ private String exceptionMessage;
+ private String exceptionLogs;
+
+ private Measure() {
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(String timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String getBranchName() {
+ return branchName;
+ }
+
+ public void setBranchName(String branchName) {
+ this.branchName = branchName;
+ }
+
+ public String getCommit() {
+ return commit;
+ }
+
+ public void setCommit(String commit) {
+ this.commit = commit;
+ }
+
+ public String getBuild() {
+ return build;
+ }
+
+ public void setBuild(String build) {
+ this.build = build;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ public String getTestClass() {
+ return testClass;
+ }
+
+ public void setTestClass(String testClass) {
+ this.testClass = testClass;
+ }
+
+ public String getTestMethod() {
+ return testMethod;
+ }
+
+ public void setTestMethod(String testMethod) {
+ this.testMethod = testMethod;
+ }
+
+ public String getExceptionClass() {
+ return exceptionClass;
+ }
+
+ public void setExceptionClass(String exceptionClass) {
+ this.exceptionClass = exceptionClass;
+ }
+
+ public String getExceptionMessage() {
+ return exceptionMessage;
+ }
+
+ public void setExceptionMessage(String exceptionMessage) {
+ this.exceptionMessage = exceptionMessage;
+ }
+
+ public String getExceptionLogs() {
+ return exceptionLogs;
+ }
+
+ public void setExceptionLogs(String exceptionLogs) {
+ this.exceptionLogs = exceptionLogs;
+ }
+
+ @Override
+ public String toString() {
+ return "Measure{" +
+ "timestamp='" + timestamp + '\'' +
+ ", branchName='" + branchName + '\'' +
+ ", commit='" + commit + '\'' +
+ ", build='" + build + '\'' +
+ ", category='" + category + '\'' +
+ ", testClass='" + testClass + '\'' +
+ ", testMethod='" + testMethod + '\'' +
+ ", exceptionClass='" + exceptionClass + '\'' +
+ ", exceptionMessage='" + exceptionMessage + '\'' +
+ ", exceptionLogs='" + exceptionLogs + '\'' +
+ '}';
+ }
+
+ public static final class MeasureBuilder {
+ private Measure measure;
+
+ private MeasureBuilder() {
+ measure = new Measure();
+ }
+
+ public static MeasureBuilder newMeasureBuilder() {
+ return new MeasureBuilder();
+ }
+
+ public MeasureBuilder setTimestamp(String timestamp) {
+ measure.setTimestamp(timestamp);
+ return this;
+ }
+
+ public MeasureBuilder setBranchName(String branchName) {
+ measure.setBranchName(branchName);
+ return this;
+ }
+
+ public MeasureBuilder setCommit(String commit) {
+ measure.setCommit(commit);
+ return this;
+ }
+
+ public MeasureBuilder setBuild(String build) {
+ measure.setBuild(build);
+ return this;
+ }
+
+ public MeasureBuilder setCategory(String category) {
+ measure.setCategory(category);
+ return this;
+ }
+
+ public MeasureBuilder setTestClass(String testClass) {
+ measure.setTestClass(testClass);
+ return this;
+ }
+
+ public MeasureBuilder setTestMethod(String testMethod) {
+ measure.setTestMethod(testMethod);
+ return this;
+ }
+
+ public MeasureBuilder setExceptionClass(String exceptionClass) {
+ measure.setExceptionClass(exceptionClass);
+ return this;
+ }
+
+ public MeasureBuilder setExceptionMessage(String exceptionMessage) {
+ measure.setExceptionMessage(exceptionMessage);
+ return this;
+ }
+
+ public MeasureBuilder setExceptionLogs(String exceptionLogs) {
+ measure.setExceptionLogs(exceptionLogs);
+ return this;
+ }
+
+ public Measure build() {
+ return measure;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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.sonarqube.monitoring.test.aspect;
+
+import com.google.gson.Gson;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.After;
+import org.aspectj.lang.annotation.Aspect;
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.sonarqube.monitoring.test.Measure;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.sonarqube.monitoring.test.Measure.MeasureBuilder.newMeasureBuilder;
+
+@Aspect
+public class TestFailureAspect {
+
+ public static final String BRANCH_NAME = System.getenv("GITHUB_BRANCH");
+ public static final String COMMIT_HASH = System.getenv("GIT_SHA1");
+ public static final String BUILD_NUMBER = System.getenv("BUILD_NUMBER");
+ public static final String QA_CATEGORY = System.getenv("QA_CATEGORY");
+
+ private static final Path PATH = Paths.get("/tmp/test-monitoring.log");
+ private static final Gson GSON = new Gson();
+
+ static {
+ try {
+ if (!Files.exists(PATH, LinkOption.NOFOLLOW_LINKS)) {
+ Files.createFile(PATH);
+ }
+ Files.write(PATH, "".getBytes(UTF_8));
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ @After("execution(public * org.junit.runner.notification.RunNotifier+.fireTestFailure(..))")
+ public void afterFireTestFailure(JoinPoint joinPoint) {
+ Object[] args = joinPoint.getArgs();
+ if (args.length == 1) {
+ Object arg = args[0];
+ if (arg instanceof Failure) {
+ Failure failure = (Failure) arg;
+ persistMeasure(buildMeasure(failure));
+ }
+ }
+ }
+
+ private static Measure buildMeasure(Failure failure) {
+ Throwable throwable = failure.getException();
+ Description description = failure.getDescription();
+ return newMeasureBuilder()
+ .setTimestamp(LocalDateTime.now(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
+ .setBranchName(BRANCH_NAME)
+ .setCommit(COMMIT_HASH)
+ .setBuild(BUILD_NUMBER)
+ .setCategory(QA_CATEGORY)
+ .setTestClass(description.getClassName())
+ .setTestMethod(description.getMethodName())
+ .setExceptionClass(throwable.getClass().getName())
+ .setExceptionMessage(failure.getMessage())
+ .setExceptionLogs(failure.getTrimmedTrace())
+ .build();
+ }
+
+ public static void persistMeasure(Measure measure) {
+ try {
+ Files.write(PATH, GSON.toJson(measure).getBytes(UTF_8), StandardOpenOption.APPEND);
+ Files.write(PATH, "\n".getBytes(UTF_8), StandardOpenOption.APPEND);
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonarqube.monitoring.test.aspect;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonarqube.monitoring.test;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+<aspectj>
+ <aspects>
+ <aspect name="org.sonarqube.monitoring.test.aspect.TestFailureAspect"/>
+ </aspects>
+ <weaver options="-warn:none -Xlint:ignore">
+ <include within="com.sonarsource..*"/>
+ <include within="org.sonarqube..*"/>
+ <include within="org.sonar..*"/>
+ <include within="com.sonar..*"/>
+ <include within="org.junit..*"/>
+ </weaver>
+</aspectj>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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.sonarqube.monitoring.test.aspect;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.aspectj.lang.JoinPoint;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.notification.Failure;
+
+import static java.nio.file.Files.createDirectory;
+import static java.nio.file.Files.exists;
+import static java.nio.file.Files.readAllBytes;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.junit.runner.Description.createTestDescription;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestFailureAspectTest {
+
+ private TestFailureAspect testFailureAspect;
+
+ private static final Path TMP_PATH = Path.of("/tmp");
+
+ @BeforeClass
+ public static void createTmpFolder() throws IOException {
+ if (!exists(TMP_PATH)) {
+ createDirectory(TMP_PATH);
+ }
+ }
+
+ @Before
+ public void setup() {
+ testFailureAspect = new TestFailureAspect();
+ }
+
+ @Test
+ public void afterFireTestFailure_shouldPersistMeasure() {
+ JoinPoint joinPoint = mock(JoinPoint.class);
+ Failure failure = new Failure(
+ createTestDescription("testClass", "testMethod"),
+ new IllegalStateException("some exception"));
+ when(joinPoint.getArgs()).thenReturn(new Object[]{failure});
+
+ testFailureAspect.afterFireTestFailure(joinPoint);
+
+ String fileContent = getFileContent(Paths.get("/tmp/test-monitoring.log"));
+ assertThat(fileContent)
+ .contains("\"timestamp\":\"" )
+ .contains("\"testClass\":\"testClass\"")
+ .contains("\"testMethod\":\"testMethod\"")
+ .contains("\"exceptionClass\":\"java.lang.IllegalStateException\"")
+ .contains("\"exceptionMessage\":\"some exception\"")
+ .contains("\"exceptionLogs\":\"java.lang.IllegalStateException: some exception");
+ }
+
+ private String getFileContent(Path path) {
+ try {
+ byte[] bytes = readAllBytes(path);
+ return new String(bytes, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ fail("Unable to read file " + path, e);
+ }
+ return null;
+ }
+
+}
gradle.preferredEnvironment=wrapper
gradle.innerModulesAsDependencies=True
gradle.ignoredConfigurations=.*test.* .*bbt.*
-gradle.excludeModules=.*it-.* .*ut-monitoring.* .*sonar-ws-generator.* .*sonar-testing-ldap.* .*sonar-testing-harness.*
+gradle.excludeModules=.*it-.* .*ut-monitoring.* .*test-monitoring.* .*sonar-ws-generator.* .*sonar-testing-ldap.* .*sonar-testing-harness.*
npm.includeDevDependencies=False
npm.resolveDependencies=True