-
Notifications
You must be signed in to change notification settings - Fork 466
Align estimated time savings between Result
and SourcesFileResults
#6022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
pstreef
wants to merge
12
commits into
main
Choose a base branch
from
feat/align-estimated-time-savings
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 3 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
adf6644
Align estimated time savings between `Result` and `SourcesFileResults`
pstreef 82122ae
add test
pstreef dc8459a
Clear out empty lines
timtebeek 4f190f2
Simplify test
pstreef f215f5c
Simplify test
pstreef c250387
formatting
pstreef d2180e4
Remove the entire hierarchy in `SourcesFileResults` and add a comment…
pstreef b2a0144
revert nullable
pstreef db38181
Merge branch 'main' into feat/align-estimated-time-savings
pstreef fb4ff7e
simplify
pstreef 841fced
Merge branch 'main' into feat/align-estimated-time-savings
pstreef bc983e0
Merge branch 'main' into feat/align-estimated-time-savings
pstreef File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,11 @@ public class Result { | |
@Getter | ||
private final Collection<List<Recipe>> recipes; | ||
|
||
/** Sum of estimated time savings of all recipes that made changes to this source file. | ||
* This is the potential time savings because if the before and after are identical, then no time is actually saved. | ||
* This does not take multiple occurrences of the same change into account and just sums the estimated time savings | ||
* of a single occurrence for each recipe that made a change. | ||
*/ | ||
private final Duration potentialTimeSavings; | ||
private @Nullable Duration timeSavings; | ||
|
||
|
@@ -68,8 +73,7 @@ public Result(@Nullable SourceFile before, @Nullable SourceFile after, Collectio | |
if (recipesStack != null && !recipesStack.isEmpty()) { | ||
Duration perOccurrence = recipesStack.get(recipesStack.size() - 1).getEstimatedEffortPerOccurrence(); | ||
if (perOccurrence != null) { | ||
timeSavings = perOccurrence; | ||
break; | ||
timeSavings = timeSavings.plus(perOccurrence); | ||
} | ||
} | ||
} | ||
|
@@ -138,6 +142,10 @@ private static boolean subtreeChanged(Tree root, Map<UUID, Tree> beforeTrees) { | |
}.reduce(root, new AtomicBoolean(false)).get(); | ||
} | ||
|
||
/** Sum of estimated time savings of all recipes that made changes to this source file. | ||
|
||
* This does not take multiple occurrences of the same change into account and just sums the estimated time savings | ||
* of a single occurrence for each recipe that made a change. | ||
*/ | ||
public Duration getTimeSavings() { | ||
if (timeSavings == null) { | ||
if (potentialTimeSavings.isZero() || isLocalAndHasNoChanges(before, after)) { | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
import lombok.Value; | ||
import org.jspecify.annotations.Nullable; | ||
import org.junit.jupiter.api.Test; | ||
import org.openrewrite.config.CompositeRecipe; | ||
import org.openrewrite.marker.AlreadyReplaced; | ||
import org.openrewrite.marker.GitProvenance; | ||
import org.openrewrite.table.DistinctGitProvenance; | ||
|
@@ -30,6 +31,7 @@ | |
import java.nio.file.Path; | ||
import java.time.Duration; | ||
import java.util.Collection; | ||
import java.util.Comparator; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
@@ -129,6 +131,99 @@ void customEstimatedEffortForRecipeThatGeneratesSourceFiles() { | |
)); | ||
} | ||
|
||
@Test | ||
void summedEstimatedEffortForMultipleChangesToSameFile() { | ||
|
||
// Create a composite recipe with 2 recipes that each have different estimated efforts | ||
Recipe recipe1 = new CustomEstimatedEffortFindReplaceRecipe("foo", "bar", Duration.ofMinutes(10)); | ||
Recipe recipe2 = new CustomEstimatedEffortFindReplaceRecipe("baz", "qux", Duration.ofMinutes(5)); | ||
|
||
Recipe compositeRecipe = new CompositeRecipe(List.of(recipe1, recipe2)); | ||
|
||
rewriteRun( | ||
recipeSpec -> recipeSpec.recipe(compositeRecipe) | ||
.afterRecipe(run -> { | ||
List<Result> results = run.getChangeset().getAllResults(); | ||
// File 1 has no changes, so it won't appear in the results | ||
assertThat(results).hasSize(3); | ||
|
||
// Sort results by path for consistent ordering | ||
results.sort(Comparator.comparing(r -> r.getAfter() != null ? | ||
r.getAfter().getSourcePath().toString() : "")); | ||
|
||
// File 2: Only first change (10 minutes) | ||
Result file2Result = results.get(0); | ||
assertThat(file2Result.getAfter()).isNotNull(); | ||
assertThat(file2Result.getAfter().getSourcePath().toString()).endsWith("file2.txt"); | ||
assertThat(file2Result.getTimeSavings()).isEqualTo(Duration.ofMinutes(10)); | ||
|
||
// File 3: Only second change (5 minutes) | ||
Result file3Result = results.get(1); | ||
assertThat(file3Result.getAfter()).isNotNull(); | ||
assertThat(file3Result.getAfter().getSourcePath().toString()).endsWith("file3.txt"); | ||
assertThat(file3Result.getTimeSavings()).isEqualTo(Duration.ofMinutes(5)); | ||
|
||
// File 4: Both changes (10 + 5 = 15 minutes) | ||
Result file4Result = results.get(2); | ||
assertThat(file4Result.getAfter()).isNotNull(); | ||
assertThat(file4Result.getAfter().getSourcePath().toString()).endsWith("file4.txt"); | ||
assertThat(file4Result.getTimeSavings()).isEqualTo(Duration.ofMinutes(15)); | ||
}) | ||
.dataTable(SourcesFileResults.Row.class, rows -> { | ||
// The data table will have multiple rows per file (one for each recipe that made changes) | ||
// File 1: no rows (no changes) | ||
// File 2: 1 row for recipe1 | ||
// File 3: 1 row for recipe2 | ||
// File 4: 2 rows (one for recipe1, one for recipe2) | ||
// Total: 4 rows | ||
assertThat(rows).hasSize(4); | ||
|
||
// Check that each row has the correct estimated time saving for its recipe | ||
long totalEstimatedTimeSaving = 0; | ||
for (SourcesFileResults.Row row : rows) { | ||
if (row.getRecipe().contains("SimpleTextReplaceRecipe")) { | ||
// First recipe has 10 minutes = 600 seconds | ||
if (row.getRecipe().contains("foo")) { | ||
assertThat(row.getEstimatedTimeSaving()).isEqualTo(600L); | ||
} | ||
// Second recipe has 5 minutes = 300 seconds | ||
else if (row.getRecipe().contains("baz")) { | ||
assertThat(row.getEstimatedTimeSaving()).isEqualTo(300L); | ||
} | ||
} | ||
if (row.getEstimatedTimeSaving() != null) { | ||
totalEstimatedTimeSaving += row.getEstimatedTimeSaving(); | ||
} | ||
} | ||
|
||
// Verify the sum of all rows' estimated time savings | ||
// File 2: 1 recipe1 (600s) | ||
// File 3: 1 recipe2 (300s) | ||
// File 4: 1 recipe1 (600s) + 1 recipe2 (300s) | ||
// Total: 600 + 300 + 600 + 300 = 1800 seconds (30 minutes) | ||
assertThat(totalEstimatedTimeSaving).isEqualTo(1800L); | ||
}), | ||
text( | ||
"nothing here", | ||
spec -> spec.path("file1.txt") | ||
), | ||
text( | ||
"foo is here twice, look: foo", | ||
"bar is here twice, look: bar", | ||
spec -> spec.path("file2.txt") | ||
), | ||
text( | ||
"baz is here", | ||
"qux is here", | ||
spec -> spec.path("file3.txt") | ||
), | ||
text( | ||
"foo and baz are here", | ||
"bar and qux are here", | ||
spec -> spec.path("file4.txt") | ||
) | ||
); | ||
} | ||
|
||
private static void assertEstimatedEffortInFirstRowOfSourceFileResults(List<SourcesFileResults.Row> rows, Long expectedEstimatedEffort) { | ||
assertThat(rows) | ||
.first() | ||
|
@@ -187,6 +282,43 @@ public Duration getEstimatedEffortPerOccurrence() { | |
} | ||
} | ||
|
||
@EqualsAndHashCode(callSuper = false) | ||
@Value | ||
private static class CustomEstimatedEffortFindReplaceRecipe extends Recipe { | ||
String find; | ||
String replace; | ||
Duration estimatedEffort; | ||
|
||
@Override | ||
public String getDisplayName() { | ||
return "Simple text replace"; | ||
} | ||
|
||
@Override | ||
public String getDescription() { | ||
return "Replaces text in files"; | ||
} | ||
|
||
@Override | ||
public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
return new PlainTextVisitor<>() { | ||
@Override | ||
public PlainText visitText(PlainText text, ExecutionContext ctx) { | ||
String newText = text.getText().replace(find, replace); | ||
if (!newText.equals(text.getText())) { | ||
return text.withText(newText); | ||
} | ||
return text; | ||
} | ||
}; | ||
} | ||
|
||
@Override | ||
public Duration getEstimatedEffortPerOccurrence() { | ||
return estimatedEffort; | ||
} | ||
} | ||
|
||
@EqualsAndHashCode(callSuper = false) | ||
@Value | ||
private static class CustomEstimatedEffortCreateTextFile extends ScanningRecipe<AtomicBoolean> { | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fundamentally alters the intention of the calculation from only summing one of the recipes in the stack to each recipe in the stack contributing time savings. I think we intentionally did not design it this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, which is why I noted there might be a good reason for this in the description. Either way not summing these here is what introduces the difference in what
SourcesFileResult
summing (if done correctly) gives you. This is confusing people.I think neither approach is "fully correct" because neither takes occurences into account, however, there currently isn't a viable mechanism to do that.
I wonder if so, why it was designed this way. Why are we picking the first? And nog the sum or the highest value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now re-reading what you said. We're not summing the recipes in 1 stack, but all the last (leaf) recipes in each stack. So that if multiple recipe (stacks) made a change to a file, we sum those estimated times up. Just like we would in the (corrected)
SourcesFileResults