From 98e2c7aa2d7d9abf737d52b863b46f774506c2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Fri, 8 May 2026 08:29:34 +0200 Subject: [PATCH 01/11] Implement rule S8692 # Conflicts: # sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json --- .../resources/autoscan/diffs/diff_S3577.json | 2 +- .../resources/autoscan/diffs/diff_S8692.json | 6 ++ .../resources/eclipse-jetty/java-S8692.json | 7 ++ .../checks/tests/SystemClockCheckSample.java | 66 ++++++++++++ .../java/checks/tests/SystemClockCheck.java | 64 +++++++++++ .../checks/tests/SystemClockCheckTest.java | 34 ++++++ .../org/sonar/l10n/java/rules/java/S8692.html | 101 ++++++++++++++++++ .../org/sonar/l10n/java/rules/java/S8692.json | 26 +++++ .../java/rules/java/Sonar_way_profile.json | 1 + 9 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S8692.json create mode 100644 its/ruling/src/test/resources/eclipse-jetty/java-S8692.json create mode 100644 java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S3577.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S3577.json index ec7fce02570..26e2fe03d03 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S3577.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S3577.json @@ -1,6 +1,6 @@ { "ruleKey": "S3577", "hasTruePositives": true, - "falseNegatives": 46, + "falseNegatives": 47, "falsePositives": 0 } diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8692.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8692.json new file mode 100644 index 00000000000..f7cae943320 --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8692.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S8692", + "hasTruePositives": true, + "falseNegatives": 0, + "falsePositives": 0 +} \ No newline at end of file diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json b/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json new file mode 100644 index 00000000000..0e39ca36907 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json @@ -0,0 +1,7 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java": [ +80, +87, +91 +] +} diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java new file mode 100644 index 00000000000..3c213965a02 --- /dev/null +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -0,0 +1,66 @@ +package checks.tests; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +class SystemClockCheckSample { + + @Mock + private Clock clock; + + static class SecurityService { + private final Clock clock; + + public SecurityService(Clock clock) { + this.clock = clock; + } + + public boolean isTokenValid(Instant issuedAt) { + return issuedAt.isAfter(Instant.now(clock).minus(1, ChronoUnit.HOURS)); + } + } + + @Test + void testSystemClockInstants() { + Instant now = Instant.now(); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} +// ^^^^^^^^^^^^^ + Instant now2 = Instant.now(); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} +// ^^^^^^^^^^^^^ + assertTrue(now.isBefore(now2)); + } + + @Test + void testInjectSystemClock() { + SecurityService securityService = new SecurityService(Clock.systemDefaultZone()); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^ + assertTrue(securityService.isTokenValid(Instant.now(Clock.system(ZoneId.systemDefault())).minusSeconds(60))); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + } + + @Test + void testFixedClock() { + Instant start = Instant.now(Clock.fixed(Instant.parse("2026-05-07T10:00:00Z"), ZoneOffset.UTC)); // Compliant + Instant later = start.plus(1, ChronoUnit.MINUTES); + assertTrue(later.isBefore(start)); + } + + @Test + void testInjectFixedClock() { + Instant fixedPoint = Instant.parse("2026-05-07T10:00:00Z"); // Compliant + when(clock.instant()).thenReturn(fixedPoint); + when(clock.getZone()).thenReturn(ZoneOffset.UTC); + + SecurityService service = new SecurityService(clock); + Instant issuedAt = Instant.parse("2026-05-07T09:30:00Z"); // Compliant + assertTrue(service.isTokenValid(issuedAt)); + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java new file mode 100644 index 00000000000..51281df3bb4 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java @@ -0,0 +1,64 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks.tests; + +import org.sonar.check.Rule; +import org.sonar.java.checks.methods.AbstractMethodDetection; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; + +@Rule(key = "S8692") +public class SystemClockCheck extends AbstractMethodDetection { + + private static final MethodMatchers SYSTEM_CLOCK_MATCHERS = MethodMatchers.or( + MethodMatchers.create() + .ofTypes("java.time.LocalDate", + "java.time.LocalTime", + "java.time.LocalDateTime", + "java.time.MonthDay", + "java.time.Year", + "java.time.YearMonth", + "java.time.ZonedDateTime", + "java.time.OffsetDateTime", + "java.time.OffsetTime") + .names("now") + .addWithoutParametersMatcher() + .addParametersMatcher("java.time.ZoneId") + .build(), + MethodMatchers.create() + .ofTypes("java.time.Instant") + .names("now") + .addWithoutParametersMatcher() + .build(), + MethodMatchers.create() + .ofTypes("java.time.Clock") + .names("systemUTC", "systemDefaultZone", "system") + .withAnyParameters() + .build() + ); + + @Override + protected MethodMatchers getMethodInvocationMatchers() { + return SYSTEM_CLOCK_MATCHERS; + } + + @Override + protected void onMethodInvocationFound(MethodInvocationTree mit) { + reportIssue(mit, "Replace this use of the system clock with a fixed clock."); + } + +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java new file mode 100644 index 00000000000..c622d2db3ac --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java @@ -0,0 +1,34 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks.tests; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.testCodeSourcesPath; + +public class SystemClockCheckTest { + + @Test + public void test() { + CheckVerifier.newVerifier() + .onFile(testCodeSourcesPath("checks/tests/SystemClockCheckSample.java")) + .withCheck(new SystemClockCheck()) + .verifyIssues(); + } + +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.html new file mode 100644 index 00000000000..2fb15d601e7 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.html @@ -0,0 +1,101 @@ +

Reading the system clock directly within unit tests introduces non-determinism. Tests that rely on the current wall-clock time are "flaky": they +may pass today but fail tomorrow, or behave differently depending on the time zone and local time of the CI/CD runner.

+

To ensure that they are repeatable and predictable, .now() methods like LocalDate.now(), +LocalDateTime.now(), or Instant.now() should only be called with a fixed clock in unit tests. Clock.system(ZoneId +zone), Clock.systemDefaultZone() and Clock.systemUTC() should also be avoided in tests.

+

Why is this an issue?

+

When a test uses the system clock, it relies on an external, uncontrollable state. This makes it impossible to:

+ +

How to fix it

+

The best practice is to inject a java.time.Clock instance into the class or method being tested. In production code, +Clock.systemDefaultZone() can be used, whereas in test code, Clock.fixed(Instant fixedInstant, ZoneId zone) should be +called, or a mock Clock be injected. This ensures that tests remain deterministic regardless of when or where they are run.

+

Code examples

+

Noncompliant code example

+

The test below is flaky because assumes that the second call to Instant.now() returns an instant that is later than the first, which +might not be true depending on the environment in which the test is run.

+
+@Test
+void testTimeDifference() {
+    Instant instant1 = Instant.now();
+    Instant instant2 = Instant.now();
+    assertTrue(instant1.isBefore(instant2)); // Noncompliant
+}
+
+

In the second example below, the service uses a static call to Instant.now(). This makes it impossible to mock the time without using +advanced (and often discouraged) static mocking techniques.

+
+public class SecurityService {
+    public boolean isTokenValid(Instant issuedAt) {
+        return issuedAt.isAfter(Instant.now().minus(1, ChronoUnit.HOURS));
+    }
+}
+
+public class SecurityServiceTest {
+    @Test
+    void testTokenValidation() {
+        SecurityService service = new SecurityService();
+        assertTrue(service.isTokenValid(Instant.now().minusSeconds(60))); // Noncompliant: the test is non-deterministic and depends on the execution time.
+    }
+}
+
+

Compliant solution

+

Here, the first instant is guaranteed to be before the second, because it is fixed, and the second is explicitly set to a later moment.

+
+@Test
+void testTimeDifference() {
+    Instant instant1 = Instant.now(Clock.fixed(Instant.parse("2026-05-07T10:00:00Z"), ZoneOffset.UTC)); // Compliant
+    Instant instant2 = instant1.plus(1, ChronoUnit.MINUTES);
+    assertTrue(instant1.isBefore(instant2));
+}
+
+

In the solution below, the Clock is a field in the class (often injected by a framework like Spring). In the test, we use @Mock to control exactly +what time the service "thinks" it is.

+
+public class SecurityService {
+    private final Clock clock;
+
+    public SecurityService(Clock clock) {
+        this.clock = clock;
+    }
+
+    public boolean isTokenValid(Instant issuedAt) {
+        return issuedAt.isAfter(Instant.now(clock).minus(1, ChronoUnit.HOURS));
+    }
+}
+
+@ExtendWith(MockitoExtension.class)
+public class SecurityServiceTest {
+    @Mock
+    private Clock clock;
+
+    @Test
+    void testTokenValidationWithMock() {
+        Instant fixedPoint = Instant.parse("2026-05-07T10:00:00Z");
+
+        // Stub the mock clock to behave like a fixed clock.
+        when(clock.instant()).thenReturn(fixedPoint);
+        when(clock.getZone()).thenReturn(ZoneOffset.UTC);
+
+        SecurityService service = new SecurityService(clock);
+
+        Instant issuedAt = Instant.parse("2026-05-07T09:30:00Z");
+        assertTrue(service.isTokenValid(issuedAt)); // Compliant: this is now deterministic, as 09:30 is within 1 hour of 10:00.
+    }
+}
+
+

Resources

+

Documentation

+ +

Articles & blog posts

+ + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json new file mode 100644 index 00000000000..937d9f24326 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json @@ -0,0 +1,26 @@ +{ + "title": "The system clock should not be used in unit tests", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "1h" + }, + "tags": [ + "java8", + "datetime", + "tests" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-8692", + "sqKey": "S8692", + "scope": "Tests", + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH", + "RELIABILITY": "MEDIUM" + }, + "attribute": "CONVENTIONAL" + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json index 85288e81bd3..a4d06d107d4 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json @@ -531,6 +531,7 @@ "S8469", "S8491", "S8688", + "S8692", "S8694" ] } From 3e56304d9e9911d3db1d0c0713a761645f0d20c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Fri, 8 May 2026 14:16:26 +0200 Subject: [PATCH 02/11] Fix issues in QG --- .../org/sonar/java/checks/tests/SystemClockCheckTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java index c622d2db3ac..10ae2c97a75 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/tests/SystemClockCheckTest.java @@ -21,10 +21,10 @@ import static org.sonar.java.checks.verifier.TestUtils.testCodeSourcesPath; -public class SystemClockCheckTest { +class SystemClockCheckTest { @Test - public void test() { + void test() { CheckVerifier.newVerifier() .onFile(testCodeSourcesPath("checks/tests/SystemClockCheckSample.java")) .withCheck(new SystemClockCheck()) From 5660da23aa4de955f0ae80350d25bc0315217556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Fri, 8 May 2026 20:23:33 +0200 Subject: [PATCH 03/11] Apply review comments and do not raise on .now(Clock) --- .../checks/tests/SystemClockCheckSample.java | 21 +++++++++++++------ .../java/checks/tests/SystemClockCheck.java | 10 +++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java index 3c213965a02..080c8e957cb 100644 --- a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -2,6 +2,7 @@ import java.time.Clock; import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -30,18 +31,26 @@ public boolean isTokenValid(Instant issuedAt) { @Test void testSystemClockInstants() { - Instant now = Instant.now(); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} + Instant now = Instant.now(); // Noncompliant {{Do not use the system clock in tests.}} // ^^^^^^^^^^^^^ - Instant now2 = Instant.now(); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} -// ^^^^^^^^^^^^^ + Instant now2 = Instant.now(Clock.system(ZoneId.systemDefault())); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assertTrue(now.isBefore(now2)); } + void testLocalDateTimeTypes() { + LocalDateTime dateTime1 = LocalDateTime.now(Clock.systemUTC()); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^^^^^^^^ + LocalDateTime dateTime2 = LocalDateTime.now(); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^^^^^^^^^^ + assertTrue(dateTime1.isBefore(dateTime2)); + } + @Test void testInjectSystemClock() { - SecurityService securityService = new SecurityService(Clock.systemDefaultZone()); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} + SecurityService securityService = new SecurityService(Clock.systemDefaultZone()); // Noncompliant {{Do not use the system clock in tests.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^ - assertTrue(securityService.isTokenValid(Instant.now(Clock.system(ZoneId.systemDefault())).minusSeconds(60))); // Noncompliant {{Replace this use of the system clock with a fixed clock.}} + assertTrue(securityService.isTokenValid(Instant.now(Clock.system(ZoneId.systemDefault())).minusSeconds(60))); // Noncompliant {{Do not use the system clock in tests.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } @@ -49,7 +58,7 @@ void testInjectSystemClock() { void testFixedClock() { Instant start = Instant.now(Clock.fixed(Instant.parse("2026-05-07T10:00:00Z"), ZoneOffset.UTC)); // Compliant Instant later = start.plus(1, ChronoUnit.MINUTES); - assertTrue(later.isBefore(start)); + assertTrue(start.isBefore(later)); } @Test diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java index 51281df3bb4..38f70781b28 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java @@ -34,16 +34,12 @@ public class SystemClockCheck extends AbstractMethodDetection { "java.time.YearMonth", "java.time.ZonedDateTime", "java.time.OffsetDateTime", - "java.time.OffsetTime") + "java.time.OffsetTime", + "java.time.Instant") .names("now") .addWithoutParametersMatcher() .addParametersMatcher("java.time.ZoneId") .build(), - MethodMatchers.create() - .ofTypes("java.time.Instant") - .names("now") - .addWithoutParametersMatcher() - .build(), MethodMatchers.create() .ofTypes("java.time.Clock") .names("systemUTC", "systemDefaultZone", "system") @@ -58,7 +54,7 @@ protected MethodMatchers getMethodInvocationMatchers() { @Override protected void onMethodInvocationFound(MethodInvocationTree mit) { - reportIssue(mit, "Replace this use of the system clock with a fixed clock."); + reportIssue(mit, "Do not use the system clock in tests."); } } From 6a2c5daf0324e4d83f40fe2d03a38a3061f38fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Tue, 19 May 2026 09:13:47 +0200 Subject: [PATCH 04/11] Add System.currentTimeMillis to the list of methods that trigger an issue --- .../java/org/sonar/java/checks/tests/SystemClockCheck.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java index 38f70781b28..8ca9e8a3c13 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java @@ -44,6 +44,11 @@ public class SystemClockCheck extends AbstractMethodDetection { .ofTypes("java.time.Clock") .names("systemUTC", "systemDefaultZone", "system") .withAnyParameters() + .build(), + MethodMatchers.create() + .ofTypes("java.lang.System") + .names("currentTimeMillis") + .addWithoutParametersMatcher() .build() ); From 49f9275825592475ad22a3665244cb6eeb0e1f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Tue, 19 May 2026 13:51:42 +0200 Subject: [PATCH 05/11] Apply review bot comment and fix ruling tests --- .../commons-beanutils/java-S8692.json | 48 +++++++++++++++++++ .../resources/sonar-server/java-S8692.json | 36 ++++++++++++++ .../checks/tests/SystemClockCheckSample.java | 1 + 3 files changed, 85 insertions(+) create mode 100644 its/ruling/src/test/resources/commons-beanutils/java-S8692.json create mode 100644 its/ruling/src/test/resources/sonar-server/java-S8692.json diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S8692.json b/its/ruling/src/test/resources/commons-beanutils/java-S8692.json new file mode 100644 index 00000000000..382719c7096 --- /dev/null +++ b/its/ruling/src/test/resources/commons-beanutils/java-S8692.json @@ -0,0 +1,48 @@ +{ +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/BeanUtilsBenchCase.java": [ +186, +190, +198, +202, +219, +223, +231, +235, +252, +256, +264, +268, +285, +289, +297, +301, +318, +322, +330, +334, +352, +356, +364, +368 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/PropertyUtilsBenchCase.java": [ +175, +179, +187, +191, +208, +212, +220, +224, +241, +245, +253, +257 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/TestResultSet.java": [ +66 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/DateConverterTestBase.java": [ +105 +] +} diff --git a/its/ruling/src/test/resources/sonar-server/java-S8692.json b/its/ruling/src/test/resources/sonar-server/java-S8692.json new file mode 100644 index 00000000000..061a36d9c7c --- /dev/null +++ b/its/ruling/src/test/resources/sonar-server/java-S8692.json @@ -0,0 +1,36 @@ +{ +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java": [ +620, +621 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java": [ +76 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexTest.java": [ +107 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java": [ +1384, +1393, +1402 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java": [ +293 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java": [ +144, +146 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java": [ +41, +48, +55 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/projecttag/ws/SearchActionTest.java": [ +116 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/search/BaseDocTest.java": [ +109, +135 +] +} diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java index 080c8e957cb..47d4390d664 100644 --- a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -38,6 +38,7 @@ void testSystemClockInstants() { assertTrue(now.isBefore(now2)); } + @Test void testLocalDateTimeTypes() { LocalDateTime dateTime1 = LocalDateTime.now(Clock.systemUTC()); // Noncompliant {{Do not use the system clock in tests.}} // ^^^^^^^^^^^^^^^^^ From 1df16ef3b2ace71bb58f644c79c8356c546a7ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Tue, 19 May 2026 14:05:48 +0200 Subject: [PATCH 06/11] Fix ruling tests and add test sample for System.currentTimeMillis --- .../resources/eclipse-jetty/java-S8692.json | 29 +++++++++++++++++++ .../checks/tests/SystemClockCheckSample.java | 7 +++++ 2 files changed, 36 insertions(+) diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json b/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json index 0e39ca36907..b852b676188 100644 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json @@ -1,7 +1,36 @@ { +"org.eclipse.jetty:jetty-project:jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java": [ +139 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [ +693, +744, +801, +835, +1008, +1010, +1296, +1298, +1715, +1743 +], "org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java": [ +78, 80, +83, +85, 87, +89, 91 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java": [ +84, +90, +106, +112, +122, +128, +149, +170 ] } diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java index 47d4390d664..008bcb3682a 100644 --- a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -55,6 +55,13 @@ void testInjectSystemClock() { // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } + @Test + void testSystemMethods() { + long currentTimeMillis = System.currentTimeMillis(); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^ + long currentTimeNanos = System.nanoTime(); // Compliant: nanoTime is typically used to measure elapsed time in tests + } + @Test void testFixedClock() { Instant start = Instant.now(Clock.fixed(Instant.parse("2026-05-07T10:00:00Z"), ZoneOffset.UTC)); // Compliant From 3e55c39460c8dfbec96d4efc6017e272b5d2e81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Tue, 19 May 2026 14:42:20 +0200 Subject: [PATCH 07/11] Try to fix ruling and autoscan tests --- .../java-S8692.json | 17 +++++++++++++++++ .../checks/tests/SystemClockCheckSample.java | 1 + 2 files changed, 18 insertions(+) create mode 100644 its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S8692.json diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S8692.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S8692.json new file mode 100644 index 00000000000..de997f41178 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S8692.json @@ -0,0 +1,17 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java": [ +139 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [ +693, +744, +801, +835, +1008, +1010, +1296, +1298, +1715, +1743 +] +} diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java index 008bcb3682a..9a0fed93eb7 100644 --- a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -60,6 +60,7 @@ void testSystemMethods() { long currentTimeMillis = System.currentTimeMillis(); // Noncompliant {{Do not use the system clock in tests.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^ long currentTimeNanos = System.nanoTime(); // Compliant: nanoTime is typically used to measure elapsed time in tests + assertTrue(currentTimeMillis < currentTimeNanos); } @Test From a898d7bea28a42b661ec23ed855315e72c04c1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 20 May 2026 12:06:17 +0200 Subject: [PATCH 08/11] Apply review comment --- .../java/checks/tests/SystemClockCheckSample.java | 7 +++++++ .../sonar/java/checks/tests/SystemClockCheck.java | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java index 9a0fed93eb7..30fe6a6f381 100644 --- a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -6,6 +6,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; +import java.util.Date; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -63,6 +64,12 @@ void testSystemMethods() { assertTrue(currentTimeMillis < currentTimeNanos); } + @Test + void testDate() { + Date date = new Date(); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^ + } + @Test void testFixedClock() { Instant start = Instant.now(Clock.fixed(Instant.parse("2026-05-07T10:00:00Z"), ZoneOffset.UTC)); // Compliant diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java index 8ca9e8a3c13..02e98952263 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java @@ -20,10 +20,13 @@ import org.sonar.java.checks.methods.AbstractMethodDetection; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.NewClassTree; @Rule(key = "S8692") public class SystemClockCheck extends AbstractMethodDetection { + private static final String MESSAGE = "Do not use the system clock in tests."; + private static final MethodMatchers SYSTEM_CLOCK_MATCHERS = MethodMatchers.or( MethodMatchers.create() .ofTypes("java.time.LocalDate", @@ -49,6 +52,11 @@ public class SystemClockCheck extends AbstractMethodDetection { .ofTypes("java.lang.System") .names("currentTimeMillis") .addWithoutParametersMatcher() + .build(), + MethodMatchers.create() + .ofTypes("java.util.Date") + .constructor() + .withAnyParameters() .build() ); @@ -57,9 +65,14 @@ protected MethodMatchers getMethodInvocationMatchers() { return SYSTEM_CLOCK_MATCHERS; } + @Override + protected void onConstructorFound(NewClassTree newClass) { + reportIssue(newClass, MESSAGE); + } + @Override protected void onMethodInvocationFound(MethodInvocationTree mit) { - reportIssue(mit, "Do not use the system clock in tests."); + reportIssue(mit, MESSAGE); } } From dd6a035f0a9504ecae9c762ba5f2043cef5f3542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 20 May 2026 13:45:27 +0200 Subject: [PATCH 09/11] Apply review comments and update ruling tests --- .../commons-beanutils/java-S8692.json | 18 ++- .../resources/eclipse-jetty/java-S8692.json | 4 + .../resources/sonar-server/java-S8692.json | 128 +++++++++++++++++- .../checks/tests/SystemClockCheckSample.java | 3 + .../java/checks/tests/SystemClockCheck.java | 2 +- 5 files changed, 152 insertions(+), 3 deletions(-) diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S8692.json b/its/ruling/src/test/resources/commons-beanutils/java-S8692.json index 382719c7096..0342fb57120 100644 --- a/its/ruling/src/test/resources/commons-beanutils/java-S8692.json +++ b/its/ruling/src/test/resources/commons-beanutils/java-S8692.json @@ -25,6 +25,15 @@ 364, 368 ], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/ConvertUtilsTestCase.java": [ +643 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/DynaBeanMapDecoratorTestCase.java": [ +50 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/DynaRowSetTestCase.java": [ +403 +], "commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/PropertyUtilsBenchCase.java": [ 175, 179, @@ -43,6 +52,13 @@ 66 ], "commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/DateConverterTestBase.java": [ -105 +105, +379 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/NumberConverterTestBase.java": [ +321 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/SqlTimestampConverterTestCase.java": [ +59 ] } diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json b/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json index b852b676188..f0ccce9797b 100644 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S8692.json @@ -16,11 +16,15 @@ ], "org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java": [ 78, +79, 80, +82, 83, 85, +86, 87, 89, +90, 91 ], "org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java": [ diff --git a/its/ruling/src/test/resources/sonar-server/java-S8692.json b/its/ruling/src/test/resources/sonar-server/java-S8692.json index 061a36d9c7c..26c5a91e6ad 100644 --- a/its/ruling/src/test/resources/sonar-server/java-S8692.json +++ b/its/ruling/src/test/resources/sonar-server/java-S8692.json @@ -1,13 +1,102 @@ { +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/authentication/JwtSerializerTest.java": [ +74, +88, +115, +254 +], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java": [ +609, 620, 621 ], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/component/ComponentCleanerServiceTest.java": [ +137 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/component/ws/TreeActionTest.java": [ +397 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCounterTest.java": [ +332 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java": [ +47 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/EvaluationResultTextConverterTest.java": [ +135, +158 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ComputeQProfileMeasureStepTest.java": [ +150 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityProfileEventsStepTest.java": [ +270 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/step/UpdateQualityProfilesLastUsedDateStepTest.java": [ +160 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java": [ +137 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/AssignActionTest.java": [ +59 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/IssueFieldsSetterTest.java": [ +50 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/IssueQueryTest.java": [ +49, +50, +51 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/IssueUpdaterTest.java": [ +92, +107, +132, +155, +179 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/IssuesFinderSortTest.java": [ +111, +133, +155 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java": [ +118 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/TransitionActionTest.java": [ +71 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/TransitionServiceTest.java": [ +95 +], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java": [ 76 ], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexTest.java": [ -107 +107, +1254 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java": [ +244 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest.java": [ +63 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java": [ +68 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java": [ +142, +160, +178, +196, +215, +231, +249 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/custom/ws/SearchActionTest.java": [ +183 ], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java": [ 1384, @@ -21,16 +110,53 @@ 144, 146 ], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/organization/TestDefaultOrganizationProvider.java": [ +39 +], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java": [ 41, 48, 55 ], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/platform/ws/DbMigrationStatusActionTest.java": [ +72 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/platform/ws/IndexActionTest.java": [ +64, +77 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/platform/ws/MigrateDbActionTest.java": [ +63 +], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/projecttag/ws/SearchActionTest.java": [ 116 ], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/qualityprofile/ws/InheritanceActionTest.java": [ +257, +270 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/rule/RuleCreatorTest.java": [ +471, +472, +489, +490, +506, +507 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/rule/ws/ShowActionMediumTest.java": [ +270, +271, +289, +290 +], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/search/BaseDocTest.java": [ 109, 135 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/test/index/TestIndexerTest.java": [ +120 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java": [ +560 ] } diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java index 30fe6a6f381..16a16c824b5 100644 --- a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; +import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @@ -68,6 +69,8 @@ void testSystemMethods() { void testDate() { Date date = new Date(); // Noncompliant {{Do not use the system clock in tests.}} // ^^^^^^^^^^ + Date date2 = new Date(100000); // Compliant + assertEquals(date, date2); } @Test diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java index 02e98952263..eb842995c49 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java @@ -56,7 +56,7 @@ public class SystemClockCheck extends AbstractMethodDetection { MethodMatchers.create() .ofTypes("java.util.Date") .constructor() - .withAnyParameters() + .addWithoutParametersMatcher() .build() ); From 2650b758c5aeeb375cfb359e834cbfe4d863aa72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 20 May 2026 14:17:32 +0200 Subject: [PATCH 10/11] Update rule S8692 metadata --- .../resources/org/sonar/l10n/java/rules/java/S8692.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json index 937d9f24326..9918d6aec7d 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8692.json @@ -11,16 +11,16 @@ "datetime", "tests" ], - "defaultSeverity": "Major", + "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-8692", "sqKey": "S8692", "scope": "Tests", - "quickfix": "unknown", + "quickfix": "infeasible", "code": { "impacts": { "MAINTAINABILITY": "HIGH", "RELIABILITY": "MEDIUM" }, - "attribute": "CONVENTIONAL" + "attribute": "TRUSTWORTHY" } } From fbb77458af1dde3f158511f083a187f559fe36df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 20 May 2026 14:57:14 +0200 Subject: [PATCH 11/11] Add Calendar.getInstance to the list of methods to raise issues on --- .../java/checks/tests/SystemClockCheckSample.java | 15 +++++++++++++-- .../sonar/java/checks/tests/SystemClockCheck.java | 5 +++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java index 16a16c824b5..ba729fbe6c2 100644 --- a/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java +++ b/java-checks-test-sources/default/src/test/java/checks/tests/SystemClockCheckSample.java @@ -6,11 +6,14 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; +import java.util.Calendar; import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @@ -66,11 +69,19 @@ void testSystemMethods() { } @Test - void testDate() { + void testDateAndCalendar() { Date date = new Date(); // Noncompliant {{Do not use the system clock in tests.}} // ^^^^^^^^^^ Date date2 = new Date(100000); // Compliant assertEquals(date, date2); + Calendar calendar = Calendar.getInstance(); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^^^^^^^^^^^^^ + Calendar calendar2 = Calendar.getInstance(TimeZone.getDefault()); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Calendar calendar3 = Calendar.getInstance(Locale.getDefault()); // Noncompliant {{Do not use the system clock in tests.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + assertEquals(calendar.getTime(), calendar2.getTime()); + assertEquals(calendar2.getTime(), calendar3.getTime()); } @Test diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java index eb842995c49..6f9fe61f651 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/SystemClockCheck.java @@ -53,6 +53,11 @@ public class SystemClockCheck extends AbstractMethodDetection { .names("currentTimeMillis") .addWithoutParametersMatcher() .build(), + MethodMatchers.create() + .ofTypes("java.util.Calendar") + .names("getInstance") + .withAnyParameters() + .build(), MethodMatchers.create() .ofTypes("java.util.Date") .constructor()