Skip to content

Commit a35ebb6

Browse files
committed
Add LatestRecipeTest to veriflylatest.yaml being up-to-date
1 parent 2a52a41 commit a35ebb6

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.camel.upgrade.springboot;
18+
19+
import org.junit.jupiter.api.Assertions;
20+
import org.junit.jupiter.api.Test;
21+
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.nio.file.Paths;
25+
import java.util.List;
26+
import java.util.stream.Stream;
27+
28+
29+
/**
30+
* Test to ensure the latest.yaml recipe references the highest version recipe
31+
* from the camel-upgrade-recipes dependency.
32+
*
33+
* The test uses the camel-upgrade-recipes dependency as the source of truth,
34+
* finds the highest version there, and verifies that latest.yaml references
35+
* the Spring Boot migration recipe for that version (but NOT the core Camel
36+
* migration recipe, which is included transitively via the version-specific
37+
* recipe chain).
38+
*
39+
* This prevents forgetting to update latest.yaml when a new version is added
40+
* to the dependency.
41+
*
42+
* Note: This test requires running from the camel-spring-boot-upgrade-recipes
43+
* module directory with the full multi-module checkout structure intact.
44+
*/
45+
class LatestRecipeTest {
46+
47+
@Test
48+
void latestRecipeReferencesHighestVersion() throws Exception {
49+
// Get versions from camel-upgrade-recipes dependency - this is the source of truth
50+
List<Version> versions = getVersionsFromCoreRecipes();
51+
52+
if (versions.isEmpty()) {
53+
Path coreCamelRecipes = Paths.get("../camel-upgrade-recipes/src/main/resources/META-INF/rewrite")
54+
.toAbsolutePath();
55+
Assertions.fail(String.format("No core Camel version recipe files found (pattern: X.Y.yaml). " +
56+
"Searched in: %s. Ensure multi-module structure is intact.", coreCamelRecipes));
57+
}
58+
59+
Version latestVersion = versions.stream()
60+
.max(Version::compareTo)
61+
.orElseThrow(() -> new IllegalStateException("No maximum version found despite non-empty list"));
62+
63+
// Read latest.yaml directly
64+
Path latestYamlPath = Paths.get("src/main/resources/META-INF/rewrite/latest.yaml").toAbsolutePath();
65+
if (!Files.exists(latestYamlPath)) {
66+
Assertions.fail("latest.yaml not found at: " + latestYamlPath);
67+
}
68+
String latestYamlContent = Files.readString(latestYamlPath);
69+
70+
// Build recipe names using version string without dot (e.g., "4.20" -> "420")
71+
String versionWithoutDot = String.format("%d%d", latestVersion.major, latestVersion.minor);
72+
73+
// Expected Spring Boot recipe name: org.apache.camel.upgrade.camelXY.CamelSpringBootMigrationRecipe
74+
String latestSpringBootRecipeName = String.format(
75+
"org.apache.camel.upgrade.camel%s.CamelSpringBootMigrationRecipe",
76+
versionWithoutDot);
77+
78+
// Verify Spring Boot recipe is present in recipeList section
79+
if (!latestYamlContent.contains("recipeList:")) {
80+
Assertions.fail("latest.yaml does not contain 'recipeList:' section");
81+
}
82+
String recipeListSection = latestYamlContent.substring(latestYamlContent.indexOf("recipeList:"));
83+
84+
Assertions.assertTrue(recipeListSection.contains(latestSpringBootRecipeName),
85+
String.format("Expected Spring Boot recipe for version %s not found in recipeList: %s",
86+
latestVersion.toDottedString(), latestSpringBootRecipeName));
87+
88+
// Expected core Camel recipe name from dependency: org.apache.camel.upgrade.camelXY.CamelMigrationRecipe
89+
String latestCoreCamelRecipeName = String.format(
90+
"org.apache.camel.upgrade.camel%s.CamelMigrationRecipe",
91+
versionWithoutDot);
92+
93+
// Verify core Camel recipe is NOT directly present (it's included transitively)
94+
Assertions.assertFalse(recipeListSection.contains(latestCoreCamelRecipeName),
95+
String.format("Core Camel recipe for version %s should NOT be directly present in latest.yaml " +
96+
"(it's included transitively via Spring Boot recipe): %s",
97+
latestVersion.toDottedString(), latestCoreCamelRecipeName));
98+
}
99+
100+
/**
101+
* Get version objects from YAML files in the camel-upgrade-recipes dependency.
102+
* Returns only version files matching the pattern X.Y.yaml (e.g., 4.20.yaml).
103+
*
104+
* @return List of Version objects found in the dependency
105+
*/
106+
private List<Version> getVersionsFromCoreRecipes() throws Exception {
107+
// Navigate to the sibling camel-upgrade-recipes module using relative path
108+
Path coreCamelRecipes = Paths.get("../camel-upgrade-recipes/src/main/resources/META-INF/rewrite");
109+
110+
if (!Files.exists(coreCamelRecipes)) {
111+
return List.of();
112+
}
113+
114+
try (Stream<Path> paths = Files.walk(coreCamelRecipes)) {
115+
return paths.filter(Files::isRegularFile)
116+
.map(Path::getFileName)
117+
.map(Path::toString)
118+
.filter(name -> name.endsWith(".yaml") && Character.isDigit(name.charAt(0)))
119+
.map(name -> name.substring(0, name.lastIndexOf('.')))
120+
.filter(version -> version.matches("^\\d+\\.\\d+$"))
121+
.map(Version::parse)
122+
.toList();
123+
}
124+
}
125+
126+
/**
127+
* Semantic version holder for comparing X.Y version strings.
128+
*/
129+
private static class Version implements Comparable<Version> {
130+
final int major;
131+
final int minor;
132+
133+
Version(int major, int minor) {
134+
this.major = major;
135+
this.minor = minor;
136+
}
137+
138+
/**
139+
* Parse a version string in X.Y format.
140+
*
141+
* @param version version string (e.g., "4.20")
142+
* @return parsed Version object
143+
* @throws IllegalArgumentException if format is invalid
144+
*/
145+
static Version parse(String version) {
146+
String[] parts = version.split("\\.");
147+
if (parts.length != 2) {
148+
throw new IllegalArgumentException("Version must be in format X.Y, got: " + version);
149+
}
150+
try {
151+
return new Version(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
152+
} catch (NumberFormatException e) {
153+
throw new IllegalArgumentException("Invalid version format: " + version, e);
154+
}
155+
}
156+
157+
@Override
158+
public int compareTo(Version other) {
159+
int majorCompare = Integer.compare(this.major, other.major);
160+
if (majorCompare != 0) {
161+
return majorCompare;
162+
}
163+
return Integer.compare(this.minor, other.minor);
164+
}
165+
166+
/**
167+
* Returns version without dot (e.g., "420" for version 4.20).
168+
* Used for recipe package names.
169+
*/
170+
@Override
171+
public String toString() {
172+
return String.format("%d%d", major, minor);
173+
}
174+
175+
/**
176+
* Returns version with dot (e.g., "4.20").
177+
* Used for user-facing messages.
178+
*/
179+
String toDottedString() {
180+
return String.format("%d.%d", major, minor);
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)