Add support for ruff-ecosystem format comparisons with black (#8419)

Extends https://github.com/astral-sh/ruff/pull/8416 activating the
`black-and-ruff` and `black-then-ruff` formatter comparison modes for
ecosystem checks allowing us to compare changes to Black across the
ecosystem.
This commit is contained in:
Zanie Blue 2023-11-01 20:29:25 -05:00 committed by GitHub
parent 2f7e2a8de3
commit ebad36da06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 48 deletions

View file

@ -37,6 +37,12 @@ Run `ruff format` ecosystem checks comparing with changes to code that is alread
ruff-ecosystem format ruff "./target/debug/ruff" --format-comparison ruff-then-ruff
```
Run `ruff format` ecosystem checks comparing with the Black formatter:
```shell
ruff-ecosystem format black ruff -v --cache python/checkouts --format-comparison black-and-ruff
```
The default output format is markdown, which includes nice summaries of the changes. You can use `--output-format json` to display the raw data — this is
particularly useful when making changes to the ecosystem checks.

View file

@ -24,7 +24,7 @@ from ruff_ecosystem.types import (
Comparison,
Diff,
Result,
RuffError,
ToolError,
)
if TYPE_CHECKING:
@ -500,7 +500,7 @@ async def ruff_check(
*, executable: Path, path: Path, name: str, options: CheckOptions
) -> Sequence[str]:
"""Run the given ruff binary against the specified path."""
ruff_args = options.to_cli_args()
ruff_args = options.to_ruff_args()
logger.debug(f"Checking {name} with {executable} " + " ".join(ruff_args))
start = time.time()
@ -518,7 +518,7 @@ async def ruff_check(
logger.debug(f"Finished checking {name} with {executable} in {end - start:.2f}s")
if proc.returncode != 0:
raise RuffError(err.decode("utf8"))
raise ToolError(err.decode("utf8"))
# Strip summary lines so the diff is only diagnostic lines
lines = [

View file

@ -46,32 +46,34 @@ def entrypoint():
tempfile.TemporaryDirectory() if not args.cache else nullcontext(args.cache)
)
ruff_baseline = args.ruff_baseline
if not args.ruff_baseline.exists():
ruff_baseline = get_executable_path(str(args.ruff_baseline))
if not ruff_baseline:
baseline_executable = args.baseline_executable
if not args.baseline_executable.exists():
baseline_executable = get_executable_path(str(args.baseline_executable))
if not baseline_executable:
print(
f"Could not find ruff baseline executable: {args.ruff_baseline}",
f"Could not find ruff baseline executable: {args.baseline_executable}",
sys.stderr,
)
exit(1)
logger.info(
"Resolved baseline executable %s to %s", args.ruff_baseline, ruff_baseline
"Resolved baseline executable %s to %s",
args.baseline_executable,
baseline_executable,
)
ruff_comparison = args.ruff_comparison
if not args.ruff_comparison.exists():
ruff_comparison = get_executable_path(str(args.ruff_comparison))
if not ruff_comparison:
comparison_executable = args.comparison_executable
if not args.comparison_executable.exists():
comparison_executable = get_executable_path(str(args.comparison_executable))
if not comparison_executable:
print(
f"Could not find ruff comparison executable: {args.ruff_comparison}",
f"Could not find ruff comparison executable: {args.comparison_executable}",
sys.stderr,
)
exit(1)
logger.info(
"Resolved comparison executable %s to %s",
args.ruff_comparison,
ruff_comparison,
args.comparison_executable,
comparison_executable,
)
targets = DEFAULT_TARGETS
@ -89,8 +91,8 @@ def entrypoint():
main_task = asyncio.ensure_future(
main(
command=RuffCommand(args.ruff_command),
ruff_baseline_executable=ruff_baseline,
ruff_comparison_executable=ruff_comparison,
baseline_executable=baseline_executable,
comparison_executable=comparison_executable,
targets=targets,
format=OutputFormat(args.output_format),
project_dir=Path(cache),
@ -160,11 +162,11 @@ def parse_args() -> argparse.Namespace:
help="The Ruff command to test",
)
parser.add_argument(
"ruff_baseline",
"baseline_executable",
type=Path,
)
parser.add_argument(
"ruff_comparison",
"comparison_executable",
type=Path,
)

View file

@ -15,7 +15,7 @@ from unidiff import PatchSet
from ruff_ecosystem import logger
from ruff_ecosystem.markdown import markdown_project_section
from ruff_ecosystem.types import Comparison, Diff, Result, RuffError
from ruff_ecosystem.types import Comparison, Diff, Result, ToolError
if TYPE_CHECKING:
from ruff_ecosystem.projects import ClonedRepository, FormatOptions
@ -151,14 +151,16 @@ async def format_then_format(
cloned_repo: ClonedRepository,
) -> Sequence[str]:
# Run format to get the baseline
await ruff_format(
await format(
formatter=baseline_formatter,
executable=ruff_baseline_executable.resolve(),
path=cloned_repo.path,
name=cloned_repo.fullname,
options=options,
)
# Then get the diff from stdout
diff = await ruff_format(
diff = await format(
formatter=Formatter.ruff,
executable=ruff_comparison_executable.resolve(),
path=cloned_repo.path,
name=cloned_repo.fullname,
@ -176,7 +178,8 @@ async def format_and_format(
cloned_repo: ClonedRepository,
) -> Sequence[str]:
# Run format without diff to get the baseline
await ruff_format(
await format(
formatter=baseline_formatter,
executable=ruff_baseline_executable.resolve(),
path=cloned_repo.path,
name=cloned_repo.fullname,
@ -189,7 +192,8 @@ async def format_and_format(
# Then reset
await cloned_repo.reset()
# Then run format again
await ruff_format(
await format(
formatter=Formatter.ruff,
executable=ruff_comparison_executable.resolve(),
path=cloned_repo.path,
name=cloned_repo.fullname,
@ -200,8 +204,9 @@ async def format_and_format(
return diff
async def ruff_format(
async def format(
*,
formatter: Formatter,
executable: Path,
path: Path,
name: str,
@ -209,16 +214,20 @@ async def ruff_format(
diff: bool = False,
) -> Sequence[str]:
"""Run the given ruff binary against the specified path."""
ruff_args = options.to_cli_args()
logger.debug(f"Formatting {name} with {executable} " + " ".join(ruff_args))
args = (
options.to_ruff_args()
if formatter == Formatter.ruff
else options.to_black_args()
)
logger.debug(f"Formatting {name} with {executable} " + " ".join(args))
if diff:
ruff_args.append("--diff")
args.append("--diff")
start = time.time()
proc = await create_subprocess_exec(
executable.absolute(),
*ruff_args,
*args,
".",
stdout=PIPE,
stderr=PIPE,
@ -230,7 +239,7 @@ async def ruff_format(
logger.debug(f"Finished formatting {name} with {executable} in {end - start:.2f}s")
if proc.returncode not in [0, 1]:
raise RuffError(err.decode("utf8"))
raise ToolError(err.decode("utf8"))
lines = result.decode("utf8").splitlines()
return lines

View file

@ -29,8 +29,8 @@ class OutputFormat(Enum):
async def main(
command: RuffCommand,
ruff_baseline_executable: Path,
ruff_comparison_executable: Path,
baseline_executable: Path,
comparison_executable: Path,
targets: list[Project],
project_dir: Path,
format: OutputFormat,
@ -39,9 +39,11 @@ async def main(
raise_on_failure: bool = False,
) -> None:
logger.debug("Using command %s", command.value)
logger.debug("Using baseline executable at %s", ruff_baseline_executable)
logger.debug("Using comparison executable at %s", ruff_comparison_executable)
logger.debug("Using baseline executable at %s", baseline_executable)
logger.debug("Using comparison executable at %s", comparison_executable)
logger.debug("Using checkout_dir directory %s", project_dir)
if format_comparison:
logger.debug("Using format comparison type %s", format_comparison.value)
logger.debug("Checking %s targets", len(targets))
# Limit parallelism to avoid high memory consumption
@ -56,8 +58,8 @@ async def main(
limited_parallelism(
clone_and_compare(
command,
ruff_baseline_executable,
ruff_comparison_executable,
baseline_executable,
comparison_executable,
target,
project_dir,
format_comparison,
@ -98,8 +100,8 @@ async def main(
async def clone_and_compare(
command: RuffCommand,
ruff_baseline_executable: Path,
ruff_comparison_executable: Path,
baseline_executable: Path,
comparison_executable: Path,
target: Project,
project_dir: Path,
format_comparison: FormatComparison | None,
@ -125,8 +127,8 @@ async def clone_and_compare(
try:
return await compare(
ruff_baseline_executable,
ruff_comparison_executable,
baseline_executable,
comparison_executable,
options,
cloned_repo,
**kwargs,

View file

@ -12,7 +12,7 @@ def markdown_project_section(
return markdown_details(
summary=f'<a href="{project.repo.url}">{project.repo.fullname}</a> ({title})',
# Show the command used for the check
preface="<pre>ruff " + " ".join(options.to_cli_args()) + "</pre>",
preface="<pre>ruff " + " ".join(options.to_ruff_args()) + "</pre>",
content=content,
)

View file

@ -49,7 +49,7 @@ class CommandOptions(Serializable, abc.ABC):
return type(self)(**{**dataclasses.asdict(self), **kwargs})
@abc.abstractmethod
def to_cli_args(self) -> list[str]:
def to_ruff_args(self) -> list[str]:
pass
@ -70,7 +70,7 @@ class CheckOptions(CommandOptions):
# Limit the number of reported lines per rule
max_lines_per_rule: int | None = 50
def to_cli_args(self) -> list[str]:
def to_ruff_args(self) -> list[str]:
args = ["check", "--no-cache", "--exit-zero"]
if self.select:
args.extend(["--select", self.select])
@ -88,13 +88,13 @@ class CheckOptions(CommandOptions):
@dataclass(frozen=True)
class FormatOptions(CommandOptions):
"""
Ruff format options.
Format ecosystem check options.
"""
preview: bool = False
exclude: str = ""
def to_cli_args(self) -> list[str]:
def to_ruff_args(self) -> list[str]:
args = ["format"]
if self.exclude:
args.extend(["--exclude", self.exclude])
@ -102,6 +102,14 @@ class FormatOptions(CommandOptions):
args.append("--preview")
return args
def to_black_args(self) -> list[str]:
args = []
if self.exclude:
args.extend(["--exclude", self.exclude])
if self.preview:
args.append("--preview")
return args
class ProjectSetupError(Exception):
"""An error setting up a project."""

View file

@ -89,5 +89,5 @@ class Comparison(Serializable):
repo: ClonedRepository
class RuffError(Exception):
"""An error reported by Ruff."""
class ToolError(Exception):
"""An error reported by the checked executable."""