Skip to content

Commit debe78e

Browse files
committed
Add common utils CI gate
1 parent fb710bb commit debe78e

5 files changed

Lines changed: 171 additions & 17 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Pull request common utilities gate: checkstyle and unit tests for shared build-tool-neutral code.
2+
# §AR-repository-ci.1.7; §common/E2E-common-tests.2.1
3+
name: "Test common-utils"
4+
5+
on:
6+
pull_request:
7+
paths:
8+
- 'common/utils/**'
9+
- 'build-logic/common-plugins/**'
10+
- 'build-logic/settings-plugins/**'
11+
- 'build-logic/utils-plugins/**'
12+
- 'config/checkstyle.xml'
13+
- '.github/actions/**'
14+
- '.github/workflows/test-common-utils.yml'
15+
- 'gradle/libs.versions.toml'
16+
- 'settings.gradle.kts'
17+
workflow_dispatch:
18+
19+
permissions:
20+
contents: read
21+
22+
concurrency:
23+
group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}"
24+
cancel-in-progress: ${{ github.event_name == 'pull_request' || github.repository != 'graalvm/native-build-tools' }}
25+
26+
jobs:
27+
test-common-utils:
28+
name: "Test common-utils"
29+
runs-on: ${{ matrix.os }}
30+
strategy:
31+
fail-fast: false
32+
matrix:
33+
java-version: [ 17.0.12 ]
34+
os: [ ubuntu-22.04 ]
35+
steps:
36+
- name: "☁️ Checkout repository"
37+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
38+
- name: "🔧 Prepare environment"
39+
uses: ./.github/actions/prepare-environment
40+
with:
41+
java-version: ${{ matrix.java-version }}
42+
github-token: ${{ secrets.GITHUB_TOKEN }}
43+
- name: "❓ Checkstyle"
44+
run: ./gradlew :utils:checkstyleMain :utils:checkstyleTest
45+
- name: "❓ Common utilities test"
46+
run: ./gradlew :utils:test
47+
- name: "📜 Upload tests results"
48+
if: always()
49+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
50+
with:
51+
name: tests-results-${{ strategy.job-index }}-${{ matrix.java-version }}-${{ matrix.os }}
52+
path: common/utils/build/reports/tests/

common/docs/e2e.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ repository check is:
1818
For focused common validation, run the relevant included build task, such as `:utils:test`,
1919
`:graalvm-reachability-metadata:test`, or `:junit-platform-native:test` from the common build
2020
context. Use the repository CI workflows in [§root/AR-repository-ci.1.5](../../docs/spec/architecture/ci.md#15-reachability-metadata-library-pr-workflow) and
21-
[§root/AR-repository-ci.1.6](../../docs/spec/architecture/ci.md#16-junit-native-support-pr-workflow) as the merge-gate equivalents.
21+
[§root/AR-repository-ci.1.6](../../docs/spec/architecture/ci.md#16-junit-native-support-pr-workflow), and
22+
[§root/AR-repository-ci.1.7](../../docs/spec/architecture/ci.md#17-common-utilities-pr-workflow) as the merge-gate equivalents.
2223

2324
## 2. Scenario Coverage
2425

@@ -50,9 +51,13 @@ compatibility-mode support. This protects [§root/FS-native-tests.3](../../docs/
5051

5152
## 3. CI coverage
5253

54+
`test-common-utils.yml` validates the shared utility module with checkstyle and unit tests.
5355
`test-graalvm-metadata.yml` validates the reachability metadata library with checkstyle and unit
5456
tests. `test-junit-platform-native.yml` validates the JUnit native support module with checkstyle,
5557
JVM tests, and native tests. Product plugin CI also exercises common behavior through Gradle and
5658
Maven functional-test matrices. These workflow gates are specified by
57-
[§root/AR-repository-ci.1.5](../../docs/spec/architecture/ci.md#15-reachability-metadata-library-pr-workflow), [§root/AR-repository-ci.1.6](../../docs/spec/architecture/ci.md#16-junit-native-support-pr-workflow),
58-
[§root/AR-repository-ci.1.3](../../docs/spec/architecture/ci.md#13-gradle-plugin-pr-workflow), and [§root/AR-repository-ci.1.4](../../docs/spec/architecture/ci.md#14-maven-plugin-pr-workflow).
59+
[§root/AR-repository-ci.1.7](../../docs/spec/architecture/ci.md#17-common-utilities-pr-workflow),
60+
[§root/AR-repository-ci.1.5](../../docs/spec/architecture/ci.md#15-reachability-metadata-library-pr-workflow),
61+
[§root/AR-repository-ci.1.6](../../docs/spec/architecture/ci.md#16-junit-native-support-pr-workflow),
62+
[§root/AR-repository-ci.1.3](../../docs/spec/architecture/ci.md#13-gradle-plugin-pr-workflow), and
63+
[§root/AR-repository-ci.1.4](../../docs/spec/architecture/ci.md#14-maven-plugin-pr-workflow).

common/utils/src/test/java/org/graalvm/buildtools/utils/FileUtilsTest.java

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,37 @@
4646
import org.junit.jupiter.params.ParameterizedTest;
4747
import org.junit.jupiter.params.provider.ValueSource;
4848

49+
import java.io.ByteArrayInputStream;
4950
import java.io.File;
5051
import java.io.IOException;
52+
import java.io.InputStream;
53+
import java.net.HttpURLConnection;
5154
import java.net.URL;
55+
import java.net.URLConnection;
56+
import java.net.URLStreamHandler;
57+
import java.nio.charset.StandardCharsets;
5258
import java.nio.file.Files;
5359
import java.nio.file.Path;
5460
import java.nio.file.Paths;
5561
import java.util.ArrayList;
62+
import java.util.HashMap;
5663
import java.util.List;
64+
import java.util.Map;
5765
import java.util.Optional;
5866
import java.util.stream.Stream;
5967

6068
import static org.junit.jupiter.api.Assertions.assertEquals;
6169
import static org.junit.jupiter.api.Assertions.assertFalse;
6270
import static org.junit.jupiter.api.Assertions.assertTrue;
6371

72+
// Protects common utility verification for download and extraction behavior. §FS-common-libraries.8.
6473
class FileUtilsTest {
6574

6675
@Test
6776
@DisplayName("It can download a file from a URL that exists")
6877
void testDownloadOk(@TempDir Path tempDir) throws IOException {
69-
URL url = new URL("https://github.com/graalvm/native-build-tools/archive/refs/heads/master.zip");
78+
URL url = url("https://example.test/archive/master.zip", ok("archive payload")
79+
.header("Content-Disposition", "attachment; filename=native-build-tools-master.zip"));
7080
List<String> errorLogs = new ArrayList<>();
7181

7282
Optional<Path> download = FileUtils.download(url, tempDir, errorLogs::add);
@@ -80,7 +90,7 @@ void testDownloadOk(@TempDir Path tempDir) throws IOException {
8090
@Test
8191
@DisplayName("It doesn't blow up with a URL that isn't a file download")
8292
void testDownloadNoFile(@TempDir Path tempDir) throws IOException {
83-
URL url = new URL("https://httpbin.org/html");
93+
URL url = url("https://example.test/html", ok("<html></html>"));
8494
List<String> errorLogs = new ArrayList<>();
8595

8696
Optional<Path> download = FileUtils.download(url, tempDir, errorLogs::add);
@@ -94,7 +104,7 @@ void testDownloadNoFile(@TempDir Path tempDir) throws IOException {
94104
@Test
95105
@DisplayName("It doesn't blow up with a URL that does not exist")
96106
void testDownloadNotFound(@TempDir Path tempDir) throws IOException {
97-
URL url = new URL("https://google.com/notfound");
107+
URL url = url("https://example.test/notfound", response(HttpURLConnection.HTTP_NOT_FOUND, "Not Found"));
98108
List<String> errorLogs = new ArrayList<>();
99109

100110
Optional<Path> download = FileUtils.download(url, tempDir, errorLogs::add);
@@ -107,7 +117,12 @@ void testDownloadNotFound(@TempDir Path tempDir) throws IOException {
107117
@Test
108118
@DisplayName("It doesn't blow up with connection timeouts")
109119
void testDownloadTimeout(@TempDir Path tempDir) throws IOException {
110-
URL url = new URL("https://httpbin.org/delay/10");
120+
URL url = url("https://example.test/delay", new TestHttpURLConnection("https://example.test/delay") {
121+
@Override
122+
public int getResponseCode() throws IOException {
123+
throw new IOException("Read timed out");
124+
}
125+
});
111126
List<String> errorLogs = new ArrayList<>();
112127

113128
Optional<Path> download = FileUtils.download(url, tempDir, errorLogs::add);
@@ -173,4 +188,76 @@ void testExtractNonZip(String format, @TempDir Path tempDir) {
173188
assertEquals("Unsupported archive format: src/test/resources/graalvm-reachability-metadata." + format + ". Only ZIP files are supported", errorLogs.get(0));
174189
}
175190

191+
private static URL url(String spec, HttpURLConnection connection) throws IOException {
192+
return new URL(null, spec, new URLStreamHandler() {
193+
@Override
194+
protected URLConnection openConnection(URL url) {
195+
return connection;
196+
}
197+
});
198+
}
199+
200+
private static TestHttpURLConnection ok(String body) {
201+
return response(HttpURLConnection.HTTP_OK, "OK").body(body);
202+
}
203+
204+
private static TestHttpURLConnection response(int responseCode, String responseMessage) {
205+
return new TestHttpURLConnection("https://example.test", responseCode, responseMessage);
206+
}
207+
208+
private static class TestHttpURLConnection extends HttpURLConnection {
209+
private final Map<String, String> headers = new HashMap<>();
210+
private byte[] body = new byte[0];
211+
212+
TestHttpURLConnection(String spec) {
213+
this(spec, HTTP_OK, "OK");
214+
}
215+
216+
TestHttpURLConnection(String spec, int responseCode, String responseMessage) {
217+
super(toUrl(spec));
218+
this.responseCode = responseCode;
219+
this.responseMessage = responseMessage;
220+
}
221+
222+
TestHttpURLConnection header(String name, String value) {
223+
headers.put(name, value);
224+
return this;
225+
}
226+
227+
TestHttpURLConnection body(String body) {
228+
this.body = body.getBytes(StandardCharsets.UTF_8);
229+
return this;
230+
}
231+
232+
@Override
233+
public String getHeaderField(String name) {
234+
return headers.get(name);
235+
}
236+
237+
@Override
238+
public InputStream getInputStream() {
239+
return new ByteArrayInputStream(body);
240+
}
241+
242+
@Override
243+
public void disconnect() {
244+
}
245+
246+
@Override
247+
public boolean usingProxy() {
248+
return false;
249+
}
250+
251+
@Override
252+
public void connect() {
253+
}
254+
255+
private static URL toUrl(String spec) {
256+
try {
257+
return new URL(spec);
258+
} catch (IOException e) {
259+
throw new IllegalArgumentException(e);
260+
}
261+
}
262+
}
176263
}

common/utils/src/test/java/org/graalvm/buildtools/utils/SchemaValidationUtilsTest.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class SchemaValidationUtilsTest {
5858

5959
@Test
6060
@DisplayName("validateSchemas succeeds when required schemas with exact majors are present")
61-
void validateSchemas_successExactMajors(@TempDir Path tempDir) throws IOException {
61+
void validateSchemasSuccessExactMajors(@TempDir Path tempDir) throws IOException {
6262
Path repoRoot = tempDir.resolve("repo-success");
6363
Path schemas = repoRoot.resolve("schemas");
6464
Files.createDirectories(schemas);
@@ -73,15 +73,15 @@ void validateSchemas_successExactMajors(@TempDir Path tempDir) throws IOExceptio
7373

7474
@Test
7575
@DisplayName("validateSchemas fails when 'schemas' directory is missing")
76-
void validateSchemas_missingSchemasDir(@TempDir Path tempDir) {
76+
void validateSchemasMissingSchemasDir(@TempDir Path tempDir) {
7777
Path repoRoot = tempDir.resolve("repo-missing");
7878
// Do not create 'schemas' directory
7979
assertThrows(IllegalStateException.class, () -> SchemaValidationUtils.validateSchemas(repoRoot));
8080
}
8181

8282
@Test
8383
@DisplayName("validateSchemas fails when repository provides an older required major")
84-
void validateSchemas_metadataTooOld(@TempDir Path tempDir) throws IOException {
84+
void validateSchemasMetadataTooOld(@TempDir Path tempDir) throws IOException {
8585
Path repoRoot = tempDir.resolve("repo-too-old");
8686
Path schemas = repoRoot.resolve("schemas");
8787
Files.createDirectories(schemas);
@@ -95,7 +95,7 @@ void validateSchemas_metadataTooOld(@TempDir Path tempDir) throws IOException {
9595

9696
@Test
9797
@DisplayName("validateSchemas fails when repository provides a newer required major")
98-
void validateSchemas_toolsTooOld(@TempDir Path tempDir) throws IOException {
98+
void validateSchemasToolsTooOld(@TempDir Path tempDir) throws IOException {
9999
Path repoRoot = tempDir.resolve("repo-tools-too-old");
100100
Path schemas = repoRoot.resolve("schemas");
101101
Files.createDirectories(schemas);
@@ -109,7 +109,7 @@ void validateSchemas_toolsTooOld(@TempDir Path tempDir) throws IOException {
109109

110110
@Test
111111
@DisplayName("validateSchemas fails when more schema files than supported are present (excluding reachability)")
112-
void validateSchemas_tooManySchemasExcludingReachability(@TempDir Path tempDir) throws IOException {
112+
void validateSchemasTooManySchemasExcludingReachability(@TempDir Path tempDir) throws IOException {
113113
Path repoRoot = tempDir.resolve("repo-too-many");
114114
Path schemas = repoRoot.resolve("schemas");
115115
Files.createDirectories(schemas);
@@ -126,7 +126,7 @@ void validateSchemas_tooManySchemasExcludingReachability(@TempDir Path tempDir)
126126

127127
@Test
128128
@DisplayName("validateReachabilityMetadataSchema does nothing when neither side provides the schema")
129-
void validateReachabilitySchema_neitherSideProvided(@TempDir Path tempDir) throws IOException {
129+
void validateReachabilitySchemaNeitherSideProvided(@TempDir Path tempDir) throws IOException {
130130
Path repoRoot = tempDir.resolve("repo-none");
131131
Files.createDirectories(repoRoot.resolve("schemas"));
132132

@@ -138,7 +138,7 @@ void validateReachabilitySchema_neitherSideProvided(@TempDir Path tempDir) throw
138138

139139
@Test
140140
@DisplayName("validateReachabilityMetadataSchema fails when repository provides schema but GraalVM does not")
141-
void validateReachabilitySchema_repoOnly(@TempDir Path tempDir) throws IOException {
141+
void validateReachabilitySchemaRepoOnly(@TempDir Path tempDir) throws IOException {
142142
Path repoRoot = tempDir.resolve("repo-only");
143143
Path schemas = repoRoot.resolve("schemas");
144144
Files.createDirectories(schemas);
@@ -180,7 +180,7 @@ void validateReachabilitySchemaRepoOnlyOnMacOsX64Jdk21(@TempDir Path tempDir) th
180180

181181
@Test
182182
@DisplayName("validateReachabilityMetadataSchema warns when GraalVM provides schema but repository does not")
183-
void validateReachabilitySchema_graalOnly(@TempDir Path tempDir) throws IOException {
183+
void validateReachabilitySchemaGraalOnly(@TempDir Path tempDir) throws IOException {
184184
Path repoRoot = tempDir.resolve("repo-no-schema");
185185
Files.createDirectories(repoRoot.resolve("schemas"));
186186
// No reachability schema file in repo
@@ -193,7 +193,7 @@ void validateReachabilitySchema_graalOnly(@TempDir Path tempDir) throws IOExcept
193193

194194
@Test
195195
@DisplayName("validateReachabilityMetadataSchema succeeds when versions match")
196-
void validateReachabilitySchema_match_ok(@TempDir Path tempDir) throws IOException {
196+
void validateReachabilitySchemaMatchOk(@TempDir Path tempDir) throws IOException {
197197
Path repoRoot = tempDir.resolve("repo-match");
198198
Path schemas = repoRoot.resolve("schemas");
199199
Files.createDirectories(schemas);
@@ -206,7 +206,7 @@ void validateReachabilitySchema_match_ok(@TempDir Path tempDir) throws IOExcepti
206206

207207
@Test
208208
@DisplayName("validateReachabilityMetadataSchema fails when versions mismatch")
209-
void validateReachabilitySchema_mismatch(@TempDir Path tempDir) throws IOException {
209+
void validateReachabilitySchemaMismatch(@TempDir Path tempDir) throws IOException {
210210
Path repoRoot = tempDir.resolve("repo-mismatch");
211211
Path schemas = repoRoot.resolve("schemas");
212212
Files.createDirectories(schemas);

docs/spec/architecture/ci.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ tests prepare both a build JDK and a GraalVM test JDK through [§AR-repository-c
1717
| `test-native-maven-plugin.yml` | Maven plugin, samples, common modules, workflow/action changes, and shared version catalog changes. | Maven functional tests plus GraalVM dev-build functional tests. [§AR-repository-ci.1.4](ci.md#14-maven-plugin-pr-workflow) |
1818
| `test-graalvm-metadata.yml` | Reachability metadata common module and relevant workflow/action changes. | Checkstyle and unit tests for the metadata repository library. [§AR-repository-ci.1.5](ci.md#15-reachability-metadata-library-pr-workflow) |
1919
| `test-junit-platform-native.yml` | JUnit native support and relevant workflow/action changes. | Checkstyle, JVM tests, and native tests for `common/junit-platform-native`. [§AR-repository-ci.1.6](ci.md#16-junit-native-support-pr-workflow) |
20+
| `test-common-utils.yml` | Common utilities, relevant shared build setup, and relevant workflow/action changes. | Checkstyle and unit tests for `common/utils`. [§AR-repository-ci.1.7](ci.md#17-common-utilities-pr-workflow) |
2021

2122
### 1.1 Grund validation workflow
2223

@@ -62,6 +63,15 @@ unit tests. It protects the repository query and missing-metadata behavior speci
6263
tests, and native tests. It protects the shared native-test runtime behavior specified by
6364
[§FS-native-tests.3](../functional/native-tests.md#3-native-launcher-and-feature).
6465

66+
### 1.7 Common utilities PR workflow
67+
68+
`test-common-utils.yml` validates `common/utils` with checkstyle and unit tests. It protects the
69+
shared utility behavior specified by [§common/FS-common-libraries.1](../../../common/docs/functional-spec.md#1-shared-native-image-utilities),
70+
[§common/FS-common-libraries.2](../../../common/docs/functional-spec.md#2-resource-configuration),
71+
[§common/FS-common-libraries.3](../../../common/docs/functional-spec.md#3-native-image-tracing-agent),
72+
[§common/FS-common-libraries.4](../../../common/docs/functional-spec.md#4-agent-metadata-post-processing), and
73+
[§common/FS-common-libraries.7](../../../common/docs/functional-spec.md#7-schema-validation).
74+
6575
## 2. Publication workflows
6676

6777
| Workflow | Scope | Required evidence |

0 commit comments

Comments
 (0)