Skip to content

Commit a674a3e

Browse files
authored
Fix DataTableRowWatcher to work under CsvDataTableStore (#74)
DataTableRowWatcher diffed snapshot row counts via DataTableStore.getRows(), which returns Stream.empty() under CsvDataTableStore. The watcher therefore saw zero rows in production (`mod`, SaaS) regardless of what the inner recipe actually emitted, so all six recipes that depend on it (LibraryUpgrade, ScalaVersionUpgrade, KotlinVersionUpgrade, GroovyVersionUpgrade, ParentPomUpgrade, DependencyVulnerabilityCheck) silently produced no UpgradesAndMigrations rows. Replace the read-back approach with a transparent decorator that wraps the existing store for the duration of the inner-recipe call, intercepts insertRow on the matching DataTable bucket, and forwards every call to the delegate. Works under any DataTableStore implementation.
1 parent ba91272 commit a674a3e

3 files changed

Lines changed: 154 additions & 12 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies {
3737
testImplementation("org.openrewrite:rewrite-java-21:${rewriteVersion}")
3838
testImplementation(gradleApi())
3939
testImplementation("org.openrewrite.gradle.tooling:model:${rewriteVersion}")
40+
testImplementation("de.siegmar:fastcsv:3.+")
4041
testRuntimeOnly("junit:junit:4.+")
4142
}
4243

src/main/java/io/moderne/devcenter/internal/DataTableRowWatcher.java

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,77 @@
1515
*/
1616
package io.moderne.devcenter.internal;
1717

18+
import org.jspecify.annotations.Nullable;
1819
import org.openrewrite.DataTable;
1920
import org.openrewrite.DataTableExecutionContextView;
2021
import org.openrewrite.DataTableStore;
2122
import org.openrewrite.ExecutionContext;
2223

2324
import java.util.ArrayList;
25+
import java.util.Collection;
2426
import java.util.List;
25-
26-
import static java.util.stream.Collectors.toList;
27+
import java.util.Objects;
28+
import java.util.stream.Stream;
2729

2830
public class DataTableRowWatcher<Row> {
2931
private final DataTable<Row> dataTable;
3032
private final ExecutionContext ctx;
31-
private int snapshotSize;
33+
private final List<Row> captured = new ArrayList<>();
34+
private @Nullable DataTableStore originalStore;
3235

3336
public DataTableRowWatcher(DataTable<Row> dataTable, ExecutionContext ctx) {
3437
this.dataTable = dataTable;
3538
this.ctx = ctx;
3639
}
3740

3841
public void start() {
39-
DataTableStore store = DataTableExecutionContextView.view(ctx).getDataTableStore();
40-
snapshotSize = (int) store.getRows(dataTable.getName(), dataTable.getGroup()).count();
42+
DataTableExecutionContextView view = DataTableExecutionContextView.view(ctx);
43+
originalStore = view.getDataTableStore();
44+
view.setDataTableStore(new RecordingStore(originalStore));
4145
}
4246

43-
@SuppressWarnings("unchecked")
4447
public List<Row> stop() {
45-
DataTableStore store = DataTableExecutionContextView.view(ctx).getDataTableStore();
46-
List<Row> allRows = (List<Row>) store.getRows(dataTable.getName(), dataTable.getGroup())
47-
.collect(toList());
48-
if (allRows.size() > snapshotSize) {
49-
return new ArrayList<>(allRows.subList(snapshotSize, allRows.size()));
48+
DataTableExecutionContextView.view(ctx).setDataTableStore(
49+
Objects.requireNonNull(originalStore, "stop() called before start()"));
50+
return new ArrayList<>(captured);
51+
}
52+
53+
private boolean matchesTarget(DataTable<?> table) {
54+
if (!table.getName().equals(dataTable.getName())) {
55+
return false;
56+
}
57+
return Objects.equals(bucketKey(table), bucketKey(dataTable));
58+
}
59+
60+
private static @Nullable String bucketKey(DataTable<?> table) {
61+
return table.getGroup() != null ? table.getGroup() : table.getInstanceName();
62+
}
63+
64+
private final class RecordingStore implements DataTableStore {
65+
private final DataTableStore delegate;
66+
67+
RecordingStore(DataTableStore delegate) {
68+
this.delegate = delegate;
69+
}
70+
71+
@Override
72+
public <R> void insertRow(DataTable<R> table, ExecutionContext ctx, R row) {
73+
delegate.insertRow(table, ctx, row);
74+
if (matchesTarget(table)) {
75+
@SuppressWarnings("unchecked")
76+
Row casted = (Row) row;
77+
captured.add(casted);
78+
}
79+
}
80+
81+
@Override
82+
public Stream<?> getRows(String dataTableName, @Nullable String group) {
83+
return delegate.getRows(dataTableName, group);
84+
}
85+
86+
@Override
87+
public Collection<DataTable<?>> getDataTables() {
88+
return delegate.getDataTables();
5089
}
51-
return new ArrayList<>();
5290
}
5391
}

src/test/java/io/moderne/devcenter/DevCenterTest.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,32 @@
1515
*/
1616
package io.moderne.devcenter;
1717

18+
import de.siegmar.fastcsv.reader.CommentStrategy;
19+
import de.siegmar.fastcsv.reader.CsvReader;
20+
import de.siegmar.fastcsv.reader.NamedCsvRecord;
1821
import io.moderne.devcenter.table.UpgradesAndMigrations;
1922
import org.intellij.lang.annotations.Language;
2023
import org.junit.jupiter.api.BeforeEach;
2124
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.io.TempDir;
2226
import org.junit.jupiter.params.ParameterizedTest;
2327
import org.junit.jupiter.params.provider.Arguments;
2428
import org.junit.jupiter.params.provider.MethodSource;
29+
import org.openrewrite.CsvDataTableStore;
30+
import org.openrewrite.DataTableExecutionContextView;
2531
import org.openrewrite.DocumentExample;
32+
import org.openrewrite.InMemoryExecutionContext;
2633
import org.openrewrite.Recipe;
2734
import org.openrewrite.config.Environment;
2835
import org.openrewrite.config.YamlResourceLoader;
2936
import org.openrewrite.test.RewriteTest;
3037

3138
import java.io.ByteArrayInputStream;
39+
import java.io.IOException;
3240
import java.net.URI;
3341
import java.nio.charset.StandardCharsets;
42+
import java.nio.file.Files;
43+
import java.nio.file.Path;
3444
import java.util.List;
3545
import java.util.Properties;
3646
import java.util.stream.Stream;
@@ -248,6 +258,99 @@ public List<Recipe> getRecipeList() {
248258
);
249259
}
250260

261+
@Test
262+
void libraryUpgradeUnderCsvDataTableStore(@TempDir Path tempDir) throws Exception {
263+
Path csvDir = tempDir.resolve("datatables");
264+
Files.createDirectories(csvDir);
265+
CsvDataTableStore store = new CsvDataTableStore(csvDir);
266+
InMemoryExecutionContext ctx = new InMemoryExecutionContext();
267+
DataTableExecutionContextView.view(ctx).setDataTableStore(store);
268+
269+
@Language("yaml") var recipe = """
270+
type: specs.openrewrite.org/v1beta/recipe
271+
name: io.moderne.devcenter.SpringBoot4Card
272+
displayName: Move to Spring Boot 4.0
273+
description: Spring Boot 4.0 upgrade card.
274+
recipeList:
275+
- io.moderne.devcenter.LibraryUpgrade:
276+
cardName: Move to Spring Boot 4.0
277+
groupIdPattern: org.springframework.boot
278+
artifactIdPattern: '*'
279+
version: 4.0.0
280+
upgradeRecipe: io.moderne.java.spring.boot4.UpgradeSpringBoot_4_0
281+
""";
282+
rewriteRun(
283+
spec ->
284+
spec.recipeFromYaml(recipe, "io.moderne.devcenter.SpringBoot4Card")
285+
.executionContext(ctx)
286+
.beforeRecipe(withToolingApi()),
287+
//language=Groovy
288+
buildGradle(
289+
"""
290+
plugins {
291+
id "java"
292+
}
293+
repositories {
294+
mavenCentral()
295+
}
296+
dependencies {
297+
implementation "org.springframework.boot:spring-boot-starter:3.1.2"
298+
}
299+
""",
300+
"""
301+
plugins {
302+
id "java"
303+
}
304+
repositories {
305+
mavenCentral()
306+
}
307+
dependencies {
308+
/*~~(org.springframework.boot:spring-boot-autoconfigure:3.1.2,org.springframework.boot:spring-boot-starter-logging:3.1.2,org.springframework.boot:spring-boot-starter:3.1.2,org.springframework.boot:spring-boot:3.1.2)~~>*/implementation "org.springframework.boot:spring-boot-starter:3.1.2"
309+
}
310+
"""
311+
)
312+
);
313+
store.close();
314+
315+
Path depsCsv = csvDir.resolve("org.openrewrite.maven.table.DependenciesInUse.csv");
316+
assertThat(depsCsv).exists();
317+
assertThat(readCsvRows(depsCsv))
318+
.as("Dependency is resolved and written to DependenciesInUse.csv")
319+
.anySatisfy(row -> {
320+
assertThat(row.getField("groupId")).isEqualTo("org.springframework.boot");
321+
assertThat(row.getField("artifactId")).isEqualTo("spring-boot-starter");
322+
assertThat(row.getField("version")).isEqualTo("3.1.2");
323+
});
324+
325+
Path upgradesCsv = findCsv(csvDir, "io.moderne.devcenter.table.UpgradesAndMigrations");
326+
assertThat(readCsvRows(upgradesCsv))
327+
.singleElement()
328+
.satisfies(row -> {
329+
assertThat(row.getField("card")).isEqualTo("Move to Spring Boot 4.0");
330+
assertThat(row.getField("value")).isEqualTo("Major");
331+
assertThat(row.getField("currentMinimumVersion")).isEqualTo("3.1.2");
332+
});
333+
}
334+
335+
private static List<NamedCsvRecord> readCsvRows(Path csv) throws IOException {
336+
try (CsvReader<NamedCsvRecord> reader = CsvReader.builder()
337+
.commentCharacter('#')
338+
.commentStrategy(CommentStrategy.SKIP)
339+
.ofNamedCsvRecord(csv)) {
340+
return reader.stream().toList();
341+
}
342+
}
343+
344+
private static Path findCsv(Path dir, String namePrefix) throws IOException {
345+
try (Stream<Path> files = Files.list(dir)) {
346+
return files
347+
.filter(p -> p.getFileName().toString().startsWith(namePrefix))
348+
.findFirst()
349+
.orElseThrow(() -> new AssertionError(
350+
"No CSV starting with '" + namePrefix + "' in " + dir));
351+
}
352+
}
353+
251354
@DocumentExample
252355
@Test
253356
void devcenterWithMultipleLibraryUpgradeRecipesHasCorrectData() {

0 commit comments

Comments
 (0)