Skip to content

Commit 0be9823

Browse files
committed
Run tests using reference data from purl-spec
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent 48ebc7d commit 0be9823

File tree

4 files changed

+228
-0
lines changed

4 files changed

+228
-0
lines changed

.github/workflows/maven.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ jobs:
1717

1818
- name: Checkout repository
1919
uses: actions/checkout@v4.2.2
20+
with:
21+
submodules: true
2022

2123
- name: Set up JDK 8 and ${{ matrix.java-version }}
2224
uses: actions/setup-java@v4

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ buildNumber.properties
1616
.idea
1717
*.iml
1818
##########################################################################
19+
20+
#### ignore VSCODE config
21+
.vscode/
22+
##########################################################################

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
<jmh.version>1.37</jmh.version>
142142
<json.version>20250107</json.version>
143143
<junit-bom.version>5.13.3</junit-bom.version>
144+
<lib.jackson-databind.version>2.19.2</lib.jackson-databind.version>
144145
<maven-surefire-junit5-tree-reporter.version>1.4.0</maven-surefire-junit5-tree-reporter.version>
145146
</properties>
146147

@@ -200,6 +201,11 @@
200201
<artifactId>junit-jupiter-params</artifactId>
201202
<scope>test</scope>
202203
</dependency>
204+
<dependency>
205+
<groupId>com.fasterxml.jackson.core</groupId>
206+
<artifactId>jackson-databind</artifactId>
207+
<version>${lib.jackson-databind.version}</version>
208+
</dependency>
203209
</dependencies>
204210

205211
<build>
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* MIT License
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*
22+
* SPDX-License-Identifier: MIT
23+
* Copyright (c) AboutCode, and contributors. All Rights Reserved.
24+
*/
25+
26+
package com.github.packageurl;
27+
28+
import java.net.URL;
29+
30+
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.junit.jupiter.api.Assertions.assertFalse;
32+
import static org.junit.jupiter.api.Assertions.assertTrue;
33+
34+
import java.io.IOException;
35+
import java.io.InputStream;
36+
import java.nio.file.Files;
37+
import java.nio.file.Paths;
38+
import java.nio.file.Path;
39+
import java.util.Collections;
40+
import java.util.List;
41+
import java.util.stream.Collectors;
42+
import java.util.stream.Stream;
43+
import com.fasterxml.jackson.databind.ObjectMapper;
44+
45+
import com.fasterxml.jackson.databind.JsonNode;
46+
47+
import org.junit.jupiter.params.ParameterizedTest;
48+
import org.junit.jupiter.params.provider.MethodSource;
49+
50+
import com.fasterxml.jackson.annotation.JsonProperty;
51+
import com.fasterxml.jackson.core.JsonParser;
52+
import com.fasterxml.jackson.core.ObjectCodec;
53+
import com.fasterxml.jackson.core.JsonProcessingException;
54+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
55+
56+
import com.fasterxml.jackson.databind.JsonDeserializer;
57+
import com.fasterxml.jackson.databind.DeserializationContext;
58+
59+
import java.util.Map;
60+
61+
public class PurlSpecRefTest {
62+
63+
public static class TestSuite {
64+
@JsonProperty("$schema")
65+
public String schema;
66+
public List<TestCase> tests;
67+
}
68+
69+
public static class TestCase {
70+
public String description;
71+
public String test_group;
72+
public String test_type;
73+
74+
public PurlOrComponent input;
75+
public PurlOrComponent expected_output;
76+
77+
public boolean expected_failure;
78+
public String expected_failure_reason;
79+
80+
public TestCase() {
81+
}
82+
}
83+
84+
@JsonDeserialize(using = PurlOrComponentDeserializer.class)
85+
public static class PurlOrComponent {
86+
public String purl;
87+
public PurlComponents components;
88+
}
89+
90+
public static class PurlComponents {
91+
public String type;
92+
public String namespace;
93+
public String name;
94+
public String version;
95+
public Map<String, String> qualifiers;
96+
public String subpath;
97+
}
98+
99+
public static class PurlOrComponentDeserializer extends JsonDeserializer<PurlOrComponent> {
100+
@Override
101+
public PurlOrComponent deserialize(JsonParser p, DeserializationContext ctxt)
102+
throws IOException, JsonProcessingException {
103+
ObjectCodec codec = p.getCodec();
104+
JsonNode node = codec.readTree(p);
105+
106+
PurlOrComponent value = new PurlOrComponent();
107+
108+
if (node.isTextual()) {
109+
value.purl = node.asText();
110+
} else if (node.isObject()) {
111+
value.components = codec.treeToValue(node, PurlComponents.class);
112+
}
113+
114+
return value;
115+
}
116+
}
117+
118+
static Stream<TestCase> collectTestCases() throws Exception {
119+
ObjectMapper mapper = new ObjectMapper();
120+
121+
URL dirURL = PurlSpecRefTest.class.getClassLoader().getResource("purl-spec/tests/types/");
122+
if (dirURL == null) {
123+
throw new RuntimeException("Resource directory 'purl-spec/tests/types/' not found");
124+
}
125+
126+
Path testDataPath = Paths.get(dirURL.toURI());
127+
List<Path> jsonFiles = Files.list(testDataPath)
128+
.filter(p -> p.toString().endsWith(".json"))
129+
.collect(Collectors.toList());
130+
131+
Stream.Builder<TestCase> builder = Stream.builder();
132+
133+
for (Path jsonFile : jsonFiles) {
134+
try (InputStream is = Files.newInputStream(jsonFile)) {
135+
TestSuite suite = mapper.readValue(is, TestSuite.class);
136+
suite.tests.forEach(builder::add);
137+
}
138+
}
139+
140+
return builder.build();
141+
}
142+
143+
void runRoundtripTest(TestCase testCase) throws Exception {
144+
String result;
145+
try {
146+
result = new PackageURL(testCase.input.purl).canonicalize().toString();
147+
} catch (Exception e) {
148+
assertTrue(testCase.expected_failure, "Unexpected failure: " + e.getMessage());
149+
return;
150+
}
151+
assertFalse(testCase.expected_failure, "Expected failure but parsing succeeded");
152+
153+
assertEquals(result, testCase.expected_output.purl);
154+
155+
}
156+
157+
void runBuildTest(TestCase testCase) throws Exception {
158+
PurlComponents input = testCase.input.components;
159+
String result;
160+
try {
161+
result = new PackageURL(input.type, input.namespace, input.name, input.version, input.qualifiers,
162+
input.subpath).canonicalize().toString();
163+
} catch (Exception e) {
164+
assertTrue(testCase.expected_failure, "Unexpected failure: " + e.getMessage());
165+
return;
166+
}
167+
168+
assertFalse(testCase.expected_failure, "Expected failure but build succeeded");
169+
assertEquals(result, testCase.expected_output.purl);
170+
}
171+
172+
void runParseTest(TestCase testCase) throws Exception {
173+
PackageURL result;
174+
try {
175+
result = new PackageURL(testCase.input.purl);
176+
} catch (Exception e) {
177+
assertTrue(testCase.expected_failure, "Unexpected failure: " + e.getMessage());
178+
return;
179+
}
180+
assertFalse(testCase.expected_failure, "Expected failure but parsing succeeded");
181+
182+
PurlComponents expected = testCase.expected_output.components;
183+
result.canonicalize();
184+
185+
assertEquals(expected.type, result.getType(), "Type mismatch");
186+
assertEquals(expected.namespace, result.getNamespace(), "Namespace mismatch");
187+
assertEquals(expected.name, result.getName(), "Name mismatch");
188+
assertEquals(expected.version, result.getVersion(), "Version mismatch");
189+
assertEquals(expected.subpath, result.getSubpath(), "Subpath mismatch");
190+
191+
assertEquals(
192+
expected.qualifiers != null ? expected.qualifiers : Collections.emptyMap(),
193+
result.getQualifiers(),
194+
"Qualifiers mismatch");
195+
}
196+
197+
@ParameterizedTest(name = "{0}")
198+
@MethodSource("collectTestCases")
199+
void runTest(TestCase testCase) throws Exception {
200+
switch (testCase.test_type) {
201+
case "roundtrip":
202+
runRoundtripTest(testCase);
203+
break;
204+
case "build":
205+
runBuildTest(testCase);
206+
break;
207+
case "parse":
208+
runParseTest(testCase);
209+
break;
210+
default:
211+
throw new IllegalArgumentException("Unknown test_type: " + testCase.test_type);
212+
}
213+
214+
}
215+
216+
}

0 commit comments

Comments
 (0)