Skip to content

Commit 53a6809

Browse files
committed
Add a simple check to ensure all pull request tasks are completed
1 parent 73ee9cc commit 53a6809

7 files changed

+776
-17
lines changed

pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@
140140
<artifactId>mockito-junit-jupiter</artifactId>
141141
<scope>test</scope>
142142
</dependency>
143+
<dependency>
144+
<groupId>io.quarkus</groupId>
145+
<artifactId>quarkus-junit5-mockito</artifactId>
146+
<scope>test</scope>
147+
</dependency>
143148
</dependencies>
144149
<build>
145150
<plugins>

src/main/java/org/hibernate/infra/bot/CheckPullRequestContributionRules.java

+32-8
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ private List<PullRequestCheck> createChecks(RepositoryConfig repositoryConfig, S
166166
}
167167
}
168168

169+
if ( repositoryConfig != null && repositoryConfig.pullRequestTasks != null
170+
&& repositoryConfig.pullRequestTasks.getEnabled().orElse( Boolean.FALSE ) ) {
171+
checks.add( new TasksCompletedCheck() );
172+
}
173+
169174
return checks;
170175
}
171176

@@ -282,15 +287,34 @@ protected LicenseCheck(String agreementText) {
282287
}
283288

284289
@Override
285-
public void perform(PullRequestCheckRunContext context, PullRequestCheckRunOutput output) throws IOException {
290+
public void perform(PullRequestCheckRunContext context, PullRequestCheckRunOutput output) {
291+
String body = context.pullRequest.getBody();
292+
PullRequestCheckRunRule rule = output.rule( "The pull request description must contain the license agreement text." );
293+
if ( body != null && body.contains( agreementText ) ) {
294+
rule.passed();
295+
}
296+
else {
297+
rule.failed( """
298+
The description of this pull request must contain the following license agreement text:
299+
```
300+
%s
301+
```
302+
""".formatted( agreementText ) );
303+
}
304+
}
305+
}
306+
307+
static class TasksCompletedCheck extends PullRequestCheck {
308+
309+
protected TasksCompletedCheck() {
310+
super( "Contribution — Review tasks" );
311+
}
312+
313+
@Override
314+
public void perform(PullRequestCheckRunContext context, PullRequestCheckRunOutput output) {
286315
String body = context.pullRequest.getBody();
287-
output.rule( """
288-
The pull request description must contain the following license agreement text:
289-
```
290-
%s
291-
```
292-
""".formatted( agreementText ) )
293-
.result( body != null && body.contains( agreementText ) );
316+
output.rule( "All pull request tasks should be completed." )
317+
.result( !EditPullRequestBodyAddTaskList.containsUnfinishedTasks( body ) );
294318
}
295319
}
296320

src/main/java/org/hibernate/infra/bot/EditPullRequestBodyAddTaskList.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class EditPullRequestBodyAddTaskList {
3434
private static final String START_MARKER = "<!-- Hibernate GitHub Bot task list start -->";
3535

3636
private static final String END_MARKER = "<!-- Hibernate GitHub Bot task list end -->";
37+
private static final Set<Character> REGEX_ESCAPE_CHARS = Set.of( '(', ')', '[', ']', '{', '}', '\\', '.', '?', '*', '+' );
3738

3839
@Inject
3940
DeploymentConfig deploymentConfig;
@@ -113,7 +114,20 @@ else if ( currentTasks != null ) {
113114
}
114115

115116
private boolean tasksAreTheSame(String currentTasks, String tasks) {
116-
return Patterns.compile( tasks.replace( "- [ ]", "- \\[.\\]" ).replace( "\n", "\\n" ) )
117+
StringBuilder sb = new StringBuilder();
118+
for ( char c : tasks.toCharArray() ) {
119+
if ( REGEX_ESCAPE_CHARS.contains( c ) ) {
120+
sb.append( '\\' );
121+
}
122+
if ( c == '\n' ) {
123+
sb.append( '\\' ).append( 'n' );
124+
}
125+
else {
126+
sb.append( c );
127+
}
128+
}
129+
130+
return Patterns.compile( sb.toString().replace( "- \\[ \\]", "- \\[.\\]" ) )
117131
.matcher( currentTasks )
118132
.matches();
119133
}
@@ -151,6 +165,17 @@ private void addTasks(StringBuilder sb, List<String> tasks) {
151165
sb.append( "\n" );
152166
}
153167

168+
public static boolean containsUnfinishedTasks(String body) {
169+
if ( body == null || body.isEmpty() ) {
170+
return false;
171+
}
172+
String taskBody = currentTaskBody( body );
173+
if ( taskBody == null ) {
174+
return false;
175+
}
176+
return taskBody.contains( "- [ ]" );
177+
}
178+
154179
private static String currentTaskBody(String originalBody) {
155180
// Check if the body already contains the tasks
156181
final int startIndex = originalBody.indexOf( START_MARKER );

src/test/java/org/hibernate/infra/bot/tests/AbstractPullRequestTest.java

-7
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ abstract class AbstractPullRequestTest {
1616
final GHCheckRunBuilder titleCheckRunUpdateBuilderMock = mockCheckRunBuilder();
1717
final GHCheckRunBuilder jiraCheckRunCreateBuilderMock = mockCheckRunBuilder();
1818
final GHCheckRunBuilder jiraCheckRunUpdateBuilderMock = mockCheckRunBuilder();
19-
final GHCheckRunBuilder licenseCheckRunCreateBuilderMock = mockCheckRunBuilder();
20-
final GHCheckRunBuilder licenseCheckRunUpdateBuilderMock = mockCheckRunBuilder();
2119

2220
GHCheckRunBuilder mockCheckRunBuilder() {
2321
return mock( GHCheckRunBuilder.class, withSettings().defaultAnswer( Answers.RETURNS_SELF ) );
@@ -36,11 +34,6 @@ void mockCheckRuns(GHRepository repoMock, String headSHA) throws IOException {
3634
);
3735
mockUpdateCheckRun( repoMock, 43L, jiraCheckRunUpdateBuilderMock, jiraCheckRunMock );
3836

39-
GHCheckRun licenseCheckRunMock = mock( GHCheckRun.class );
40-
mockCreateCheckRun( repoMock, "Contribution — License agreement", headSHA,
41-
licenseCheckRunCreateBuilderMock, licenseCheckRunMock, 44L
42-
);
43-
mockUpdateCheckRun( repoMock, 44L, licenseCheckRunUpdateBuilderMock, licenseCheckRunMock );
4437
}
4538

4639
void mockCreateCheckRun(GHRepository repoMock, String name, String headSHA,

src/test/java/org/hibernate/infra/bot/tests/CheckPullRequestContributionRulesLicenseTest.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static io.quarkiverse.githubapp.testing.GitHubAppTesting.given;
44
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.mockito.Mockito.mock;
56
import static org.mockito.Mockito.verify;
67
import static org.mockito.Mockito.verifyNoMoreInteractions;
78
import static org.mockito.Mockito.when;
@@ -13,6 +14,8 @@
1314

1415
import io.quarkiverse.githubapp.testing.GitHubAppTest;
1516
import io.quarkus.test.junit.QuarkusTest;
17+
import org.kohsuke.github.GHCheckRun;
18+
import org.kohsuke.github.GHCheckRunBuilder;
1619
import org.kohsuke.github.GHEvent;
1720
import org.kohsuke.github.GHPullRequest;
1821
import org.kohsuke.github.GHRepository;
@@ -23,6 +26,20 @@
2326
@GitHubAppTest
2427
@ExtendWith(MockitoExtension.class)
2528
public class CheckPullRequestContributionRulesLicenseTest extends AbstractPullRequestTest {
29+
30+
final GHCheckRunBuilder licenseCheckRunCreateBuilderMock = mockCheckRunBuilder();
31+
final GHCheckRunBuilder licenseCheckRunUpdateBuilderMock = mockCheckRunBuilder();
32+
33+
@Override
34+
void mockCheckRuns(GHRepository repoMock, String headSHA) throws IOException {
35+
super.mockCheckRuns( repoMock, headSHA );
36+
GHCheckRun licenseCheckRunMock = mock( GHCheckRun.class );
37+
mockCreateCheckRun( repoMock, "Contribution — License agreement", headSHA,
38+
licenseCheckRunCreateBuilderMock, licenseCheckRunMock, 44L
39+
);
40+
mockUpdateCheckRun( repoMock, 44L, licenseCheckRunUpdateBuilderMock, licenseCheckRunMock );
41+
}
42+
2643
@Test
2744
void bodyMissingLicenseAgreement() throws IOException {
2845
long repoId = 344815557L;
@@ -75,7 +92,8 @@ void bodyMissingLicenseAgreement() throws IOException {
7592
7693
This pull request does not follow the contribution rules. Could you have a look?
7794
78-
❌ The pull request description must contain the following license agreement text:
95+
❌ The pull request description must contain the license agreement text.
96+
    ↳ The description of this pull request must contain the following license agreement text:
7997
```
8098
----------------------
8199
By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.txt)

0 commit comments

Comments
 (0)