Skip to content

Commit 45d4753

Browse files
committed
Application CycloneDX tests
1 parent de91b69 commit 45d4753

File tree

6 files changed

+357
-0
lines changed

6 files changed

+357
-0
lines changed

integration-tests/maven/pom.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,23 @@
125125
<artifactId>quarkus-opentelemetry-deployment</artifactId>
126126
<scope>test</scope>
127127
</dependency>
128+
<dependency>
129+
<groupId>io.quarkus</groupId>
130+
<artifactId>quarkus-cyclonedx-deployment</artifactId>
131+
<scope>test</scope>
132+
<version>${project.version}</version>
133+
<exclusions>
134+
<exclusion>
135+
<groupId>*</groupId>
136+
<artifactId>*</artifactId>
137+
</exclusion>
138+
</exclusions>
139+
</dependency>
140+
<dependency>
141+
<groupId>org.cyclonedx</groupId>
142+
<artifactId>cyclonedx-core-java</artifactId>
143+
<scope>test</scope>
144+
</dependency>
128145
<dependency>
129146
<groupId>io.quarkus</groupId>
130147
<artifactId>quarkus-project-core-extension-codestarts</artifactId>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package io.quarkus.maven.it;
2+
3+
import static io.quarkus.maven.it.CycloneDxTestUtils.*;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
import java.io.File;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Properties;
10+
11+
import org.cyclonedx.model.Bom;
12+
import org.cyclonedx.model.Component;
13+
import org.junit.jupiter.api.Test;
14+
15+
import io.quarkus.maven.it.verifier.MavenProcessInvocationResult;
16+
import io.quarkus.maven.it.verifier.RunningInvoker;
17+
18+
@DisableForNative
19+
public class CycloneDxIT extends MojoTestBase {
20+
21+
private RunningInvoker running;
22+
private File testDir;
23+
24+
@Test
25+
public void testFastJar() throws Exception {
26+
testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-fast-jar");
27+
running = new RunningInvoker(testDir, false);
28+
final MavenProcessInvocationResult result = running.execute(
29+
List.of("package", "-DskipTests"),
30+
Map.of());
31+
assertThat(result.getProcess().waitFor()).isEqualTo(0);
32+
33+
final Bom bom = parseSbom(testDir, "quarkus-run-cyclonedx.json");
34+
assertRunnerMainComponent(bom);
35+
36+
final List<Component> components = bom.getComponents();
37+
assertThat(components).isNotEmpty();
38+
assertComponent(components, "io.quarkus", "quarkus-rest", "runtime", "lib/main/");
39+
assertComponent(components, "io.quarkus", "quarkus-rest-deployment", "development", null);
40+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx", "runtime", "lib/main/");
41+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx-deployment", "development", null);
42+
}
43+
44+
@Test
45+
public void testUberJar() throws Exception {
46+
testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-uber-jar");
47+
running = new RunningInvoker(testDir, false);
48+
49+
Properties p = new Properties();
50+
p.setProperty("quarkus.package.jar.type", "uber-jar");
51+
52+
final MavenProcessInvocationResult result = running.execute(
53+
List.of("package", "-DskipTests"),
54+
Map.of(), p);
55+
assertThat(result.getProcess().waitFor()).isEqualTo(0);
56+
57+
final Bom bom = parseSbom(testDir, "acme-app-1.0-SNAPSHOT-runner-cyclonedx.json");
58+
59+
final Component mainComponent = bom.getMetadata().getComponent();
60+
assertThat(mainComponent).isNotNull();
61+
assertThat(mainComponent.getGroup()).isEqualTo("org.acme");
62+
assertThat(mainComponent.getName()).isEqualTo("acme-app");
63+
assertThat(mainComponent.getVersion()).isEqualTo("1.0-SNAPSHOT");
64+
assertThat(mainComponent.getType()).isEqualTo(Component.Type.APPLICATION);
65+
assertThat(mainComponent.getPurl()).isEqualTo(
66+
"pkg:maven/org.acme/acme-app@1.0-SNAPSHOT?classifier=runner&type=jar");
67+
assertComponentScope(mainComponent, "runtime");
68+
69+
// uber-jar components have no evidence location
70+
final List<Component> components = bom.getComponents();
71+
assertThat(components).isNotEmpty();
72+
assertComponent(components, "io.quarkus", "quarkus-rest", "runtime", null);
73+
assertComponent(components, "io.quarkus", "quarkus-rest-deployment", "development", null);
74+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx", "runtime", null);
75+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx-deployment", "development", null);
76+
}
77+
78+
@Test
79+
public void testMutableJar() throws Exception {
80+
testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-mutable-jar");
81+
running = new RunningInvoker(testDir, false);
82+
83+
Properties p = new Properties();
84+
p.setProperty("quarkus.package.jar.type", "mutable-jar");
85+
86+
final MavenProcessInvocationResult result = running.execute(
87+
List.of("package", "-DskipTests"),
88+
Map.of(), p);
89+
assertThat(result.getProcess().waitFor()).isEqualTo(0);
90+
91+
final Bom bom = parseSbom(testDir, "quarkus-run-cyclonedx.json");
92+
assertRunnerMainComponent(bom);
93+
94+
// mutable-jar includes deployment jars, so both runtime and development have locations
95+
final List<Component> components = bom.getComponents();
96+
assertThat(components).isNotEmpty();
97+
assertComponent(components, "io.quarkus", "quarkus-rest", "runtime", "lib/main/");
98+
assertComponent(components, "io.quarkus", "quarkus-rest-deployment", "development", "lib/deployment/");
99+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx", "runtime", "lib/main/");
100+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx-deployment", "development", "lib/deployment/");
101+
}
102+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.quarkus.maven.it;
2+
3+
import static io.quarkus.maven.it.CycloneDxTestUtils.*;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.awaitility.Awaitility.await;
6+
7+
import java.io.File;
8+
import java.util.Collections;
9+
import java.util.List;
10+
import java.util.concurrent.TimeUnit;
11+
12+
import org.cyclonedx.model.Bom;
13+
import org.cyclonedx.model.Component;
14+
import org.junit.jupiter.api.Assumptions;
15+
import org.junit.jupiter.api.Test;
16+
17+
import io.quarkus.maven.it.verifier.MavenProcessInvocationResult;
18+
import io.quarkus.maven.it.verifier.RunningInvoker;
19+
20+
@EnableForNative
21+
public class CycloneDxNativeIT extends MojoTestBase {
22+
23+
@Test
24+
public void testNativeImage() throws Exception {
25+
final File testDir = initProject("projects/cyclonedx-sbom", "projects/cyclonedx-sbom-native");
26+
final RunningInvoker running = new RunningInvoker(testDir, false);
27+
28+
final List<String> mvnArgs = TestUtils.nativeArguments("package", "-DskipTests", "-Dnative");
29+
final MavenProcessInvocationResult result = running.execute(mvnArgs, Collections.emptyMap());
30+
await().atMost(10, TimeUnit.MINUTES).until(() -> result.getProcess() != null && !result.getProcess().isAlive());
31+
final String processLog = running.log();
32+
try {
33+
assertThat(processLog).containsIgnoringCase("BUILD SUCCESS");
34+
} catch (AssertionError ae) {
35+
Assumptions.assumeFalse(processLog.contains("Cannot find the `native-image"),
36+
"Skipping test since native-image tool isn't available");
37+
throw ae;
38+
} finally {
39+
running.stop();
40+
}
41+
42+
final Bom bom = parseSbom(testDir, "acme-app-1.0-SNAPSHOT-runner-cyclonedx.json");
43+
44+
// native image main component is a generic file component (no Maven coords)
45+
final Component mainComponent = bom.getMetadata().getComponent();
46+
assertThat(mainComponent).isNotNull();
47+
assertThat(mainComponent.getName()).isEqualTo("acme-app-1.0-SNAPSHOT-runner");
48+
assertThat(mainComponent.getVersion()).isEqualTo("1.0-SNAPSHOT");
49+
assertThat(mainComponent.getType()).isEqualTo(Component.Type.APPLICATION);
50+
assertThat(mainComponent.getPurl()).isEqualTo("pkg:generic/acme-app-1.0-SNAPSHOT-runner@1.0-SNAPSHOT");
51+
52+
final List<Component> components = bom.getComponents();
53+
assertThat(components).isNotEmpty();
54+
assertComponent(components, "io.quarkus", "quarkus-rest", "runtime", null);
55+
assertComponent(components, "io.quarkus", "quarkus-rest-deployment", "development", null);
56+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx", "runtime", null);
57+
assertComponent(components, "io.quarkus", "quarkus-cyclonedx-deployment", "development", null);
58+
}
59+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.quarkus.maven.it;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.File;
6+
import java.util.List;
7+
8+
import org.cyclonedx.model.Bom;
9+
import org.cyclonedx.model.Component;
10+
import org.cyclonedx.model.Property;
11+
import org.cyclonedx.model.component.evidence.Occurrence;
12+
import org.cyclonedx.parsers.JsonParser;
13+
14+
final class CycloneDxTestUtils {
15+
16+
private CycloneDxTestUtils() {
17+
}
18+
19+
static Bom parseSbom(File testDir, String sbomFileName) throws Exception {
20+
final File sbomFile = new File(testDir, "target/" + sbomFileName);
21+
assertThat(sbomFile).exists();
22+
return new JsonParser().parse(sbomFile);
23+
}
24+
25+
/**
26+
* Asserts that the main component is the quarkus-run.jar runner
27+
* (used by fast-jar and mutable-jar packaging).
28+
*/
29+
static void assertRunnerMainComponent(Bom bom) {
30+
final Component mainComponent = bom.getMetadata().getComponent();
31+
assertThat(mainComponent).isNotNull();
32+
assertThat(mainComponent.getName()).isEqualTo("quarkus-run.jar");
33+
assertThat(mainComponent.getVersion()).isEqualTo("1.0-SNAPSHOT");
34+
assertThat(mainComponent.getType()).isEqualTo(Component.Type.APPLICATION);
35+
assertThat(mainComponent.getPurl()).isEqualTo("pkg:generic/quarkus-run.jar@1.0-SNAPSHOT");
36+
assertComponentScope(mainComponent, "runtime");
37+
}
38+
39+
/**
40+
* Asserts that a component with the given group and name exists in the SBOM,
41+
* has the expected scope property, and optionally has an evidence location
42+
* starting with the given prefix.
43+
*
44+
* @param components the component list
45+
* @param group expected group
46+
* @param name expected artifact name
47+
* @param expectedScope expected value of the quarkus:component:scope property
48+
* @param expectedLocationPrefix if non-null, the component's evidence location must start with this prefix;
49+
* if null, no evidence location is expected
50+
*/
51+
static void assertComponent(List<Component> components, String group, String name,
52+
String expectedScope, String expectedLocationPrefix) {
53+
final Component component = components.stream()
54+
.filter(c -> group.equals(c.getGroup()) && name.equals(c.getName()))
55+
.findFirst()
56+
.orElse(null);
57+
assertThat(component)
58+
.as("Expected component %s:%s in SBOM", group, name)
59+
.isNotNull();
60+
assertComponentScope(component, expectedScope);
61+
assertEvidenceLocation(component, expectedLocationPrefix);
62+
}
63+
64+
static void assertComponentScope(Component component, String expectedScope) {
65+
final List<Property> properties = component.getProperties();
66+
assertThat(properties).isNotNull();
67+
final String scope = properties.stream()
68+
.filter(p -> "quarkus:component:scope".equals(p.getName()))
69+
.map(Property::getValue)
70+
.findFirst()
71+
.orElse(null);
72+
assertThat(scope)
73+
.as("quarkus:component:scope of %s:%s", component.getGroup(), component.getName())
74+
.isEqualTo(expectedScope);
75+
}
76+
77+
private static void assertEvidenceLocation(Component component, String expectedLocationPrefix) {
78+
if (expectedLocationPrefix == null) {
79+
if (component.getEvidence() == null || component.getEvidence().getOccurrences() == null) {
80+
return;
81+
}
82+
assertThat(component.getEvidence().getOccurrences())
83+
.as("Evidence locations of %s:%s", component.getGroup(), component.getName())
84+
.isEmpty();
85+
return;
86+
}
87+
assertThat(component.getEvidence()).isNotNull();
88+
final List<Occurrence> occurrences = component.getEvidence().getOccurrences();
89+
assertThat(occurrences)
90+
.as("Evidence locations of %s:%s", component.getGroup(), component.getName())
91+
.isNotEmpty();
92+
assertThat(occurrences.get(0).getLocation()).startsWith(expectedLocationPrefix);
93+
}
94+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version="1.0"?>
2+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>org.acme</groupId>
6+
<artifactId>acme-app</artifactId>
7+
<version>1.0-SNAPSHOT</version>
8+
<packaging>quarkus</packaging>
9+
<properties>
10+
<compiler-plugin.version>${compiler-plugin.version}</compiler-plugin.version>
11+
<maven.compiler.release>${maven.compiler.release}</maven.compiler.release>
12+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
14+
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
15+
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
16+
<quarkus.platform.version>${project.version}</quarkus.platform.version>
17+
</properties>
18+
<dependencyManagement>
19+
<dependencies>
20+
<dependency>
21+
<groupId>\${quarkus.platform.group-id}</groupId>
22+
<artifactId>\${quarkus.platform.artifact-id}</artifactId>
23+
<version>\${quarkus.platform.version}</version>
24+
<type>pom</type>
25+
<scope>import</scope>
26+
</dependency>
27+
</dependencies>
28+
</dependencyManagement>
29+
<dependencies>
30+
<dependency>
31+
<groupId>io.quarkus</groupId>
32+
<artifactId>quarkus-rest</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>io.quarkus</groupId>
36+
<artifactId>quarkus-cyclonedx</artifactId>
37+
</dependency>
38+
</dependencies>
39+
<build>
40+
<plugins>
41+
<plugin>
42+
<groupId>\${quarkus.platform.group-id}</groupId>
43+
<artifactId>quarkus-maven-plugin</artifactId>
44+
<version>\${quarkus.platform.version}</version>
45+
<extensions>true</extensions>
46+
</plugin>
47+
<plugin>
48+
<artifactId>maven-compiler-plugin</artifactId>
49+
<version>\${compiler-plugin.version}</version>
50+
<configuration>
51+
<parameters>true</parameters>
52+
</configuration>
53+
</plugin>
54+
</plugins>
55+
</build>
56+
<profiles>
57+
<profile>
58+
<id>native</id>
59+
<activation>
60+
<property>
61+
<name>native</name>
62+
</property>
63+
</activation>
64+
<properties>
65+
<quarkus.native.enabled>true</quarkus.native.enabled>
66+
</properties>
67+
</profile>
68+
</profiles>
69+
</project>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.acme;
2+
3+
import jakarta.ws.rs.GET;
4+
import jakarta.ws.rs.Path;
5+
import jakarta.ws.rs.Produces;
6+
import jakarta.ws.rs.core.MediaType;
7+
8+
@Path("/hello")
9+
public class GreetingResource {
10+
11+
@GET
12+
@Produces(MediaType.TEXT_PLAIN)
13+
public String hello() {
14+
return "Hello from Quarkus REST";
15+
}
16+
}

0 commit comments

Comments
 (0)