Skip to content

AssertJ aggregate is not idempotent: isNotSameAs(0) → isNotEqualTo(0) → isNotZero() requires two rewriteRuns #1032

Description

@juherr

What version of OpenRewrite are you using?

  • rewrite-testing-frameworks 3.39.0
  • OpenRewrite Gradle plugin 7.35.0
  • JDK 21
  • Active recipe: org.openrewrite.java.testing.assertj.Assertj

How are you running OpenRewrite?

Gradle plugin, ./gradlew rewriteRun.

What is the smallest, simplest way to reproduce the problem?

Given (here value is an int):

assertThat(value).isNotSameAs(0);

A first rewriteRun produces:

assertThat(value).isNotEqualTo(0);

Running rewriteRun again, with no source edits in between, produces:

assertThat(value).isNotZero();

So the aggregate does not reach a fixed point in a single invocation:

  1. AssertJPrimitiveRulesRecipes$AssertThatIsNotEqualToRecipe rewrites isNotSameAs(0)isNotEqualTo(0) on the first run;
  2. AssertJIntegerRulesRecipes$AbstractIntegerAssertIsNotZeroRecipe only collapses isNotEqualTo(0)isNotZero() on the next run.

Both rules are part of the same Assertj aggregate, but the output of (1) is not fed into (2) within one rewriteRun. (Same for isSameAs(0)isEqualTo(0)isZero().)

What did you expect to see?

Either the Assertj aggregate converges in a single rewriteRun, or it is documented that the recipe must be run until stable. As-is, consumers silently get sub-optimal output (isNotEqualTo(0) instead of isNotZero()) unless they happen to run the recipe twice.

What happened instead?

A single rewriteRun leaves isNotEqualTo(0) / isEqualTo(0) that a second run then simplifies to isNotZero() / isZero(). We observed this on ~5 files when migrating the TestNG suite; running the recipe a second time was required to fully converge.

Additional context

Note: the first step above (isNotSameAsisNotEqualTo) is itself questionable when the operands are reference types rather than primitives — filed separately against the rule source (PicnicSupermarket/error-prone-support). This report is specifically about the non-convergence of the aggregate in a single run, which is independent of that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions