-
Notifications
You must be signed in to change notification settings - Fork 727
SONARJAVA-6330: Implement S8692 - The system clock should not be used in tests #5611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
2a6f79f
34e5c6e
94204e9
a62e055
acd3409
8e47a3c
c9ed396
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| { | ||
| "ruleKey": "S3577", | ||
| "hasTruePositives": true, | ||
| "falseNegatives": 46, | ||
| "falseNegatives": 47, | ||
| "falsePositives": 0 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "ruleKey": "S8692", | ||
| "hasTruePositives": true, | ||
| "falseNegatives": 0, | ||
| "falsePositives": 0 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +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 | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| package checks.tests; | ||
|
|
||
| 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; | ||
| 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() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think that in the first example, we should probably raise an issue on
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if System.nanoTime doesn't read the wall-clock system time, it still uses the hardware to measure time (which still makes the tests non deterministic), so I think it could be targeted by the rule too
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, however my issue with including
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, you're right, it seems to be out of scope for this rule |
||
| Instant now = Instant.now(); // Noncompliant {{Do not use the system clock in tests.}} | ||
| // ^^^^^^^^^^^^^ | ||
| Instant now2 = Instant.now(Clock.system(ZoneId.systemDefault())); // Noncompliant {{Do not use the system clock in tests.}} | ||
| // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| assertTrue(now.isBefore(now2)); | ||
| } | ||
|
|
||
| @Test | ||
| 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 {{Do not use the system clock in tests.}} | ||
| // ^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| assertTrue(securityService.isTokenValid(Instant.now(Clock.system(ZoneId.systemDefault())).minusSeconds(60))); // Noncompliant {{Do not use the system clock in tests.}} | ||
| // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| } | ||
|
|
||
| @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 | ||
| assertTrue(currentTimeMillis < currentTimeNanos); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is an idea for another rule: if you use a suffix such as |
||
| } | ||
|
|
||
| @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(start.isBefore(later)); | ||
| } | ||
|
|
||
| @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)); | ||
| } | ||
|
|
||
| } | ||
|
sonar-review-alpha[bot] marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* | ||
| * 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", | ||
| "java.time.Instant") | ||
| .names("now") | ||
| .addWithoutParametersMatcher() | ||
| .addParametersMatcher("java.time.ZoneId") | ||
| .build(), | ||
| MethodMatchers.create() | ||
| .ofTypes("java.time.Clock") | ||
| .names("systemUTC", "systemDefaultZone", "system") | ||
| .withAnyParameters() | ||
| .build(), | ||
| MethodMatchers.create() | ||
| .ofTypes("java.lang.System") | ||
| .names("currentTimeMillis") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It just occured to me that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I'll add calls to the |
||
| .addWithoutParametersMatcher() | ||
| .build() | ||
| ); | ||
|
|
||
| @Override | ||
| protected MethodMatchers getMethodInvocationMatchers() { | ||
| return SYSTEM_CLOCK_MATCHERS; | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMethodInvocationFound(MethodInvocationTree mit) { | ||
| reportIssue(mit, "Do not use the system clock in tests."); | ||
|
aurelien-coet-sonarsource marked this conversation as resolved.
|
||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
||
| class SystemClockCheckTest { | ||
|
|
||
| @Test | ||
| void test() { | ||
| CheckVerifier.newVerifier() | ||
| .onFile(testCodeSourcesPath("checks/tests/SystemClockCheckSample.java")) | ||
| .withCheck(new SystemClockCheck()) | ||
| .verifyIssues(); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you reviewed all these sources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, these are all occurrences of
System.currentTimeMillisin tests !