Skip to content
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

ci: add test coverage for format binary #126

Merged
merged 2 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
support-version: "0.3.0"
assert-path: /usr/lib/bats/bats-assert
assert-version: "2.1.0"
- name: Integration test
- name: "Integration test: example"
working-directory: example
run: bats ./test
- name: "Integration test: format"
working-directory: format
run: bats ./test
2 changes: 2 additions & 0 deletions example/src/hello.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.h1 {
}
2 changes: 2 additions & 0 deletions example/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!DOCTYPE html>
<html></html>
11 changes: 9 additions & 2 deletions format/private/format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ if [ -n "$files" ] && [ -n "$bin" ]; then
echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode
fi

files=$(ls-files JSON $@)
bin=$(rlocation {{prettier}})
if [ -n "$files" ] && [ -n "$bin" ]; then
echo "Formatting JSON with Prettier..."
echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode
fi

files=$(ls-files JavaScript $@)
bin=$(rlocation {{prettier}})
if [ -n "$files" ] && [ -n "$bin" ]; then
Expand Down Expand Up @@ -178,14 +185,14 @@ fi
files=$(ls-files SQL $@)
bin=$(rlocation {{prettier-sql}})
if [ -n "$files" ] && [ -n "$bin" ]; then
echo "Running SQL with Prettier..."
echo "Formatting SQL with Prettier..."
echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode
fi

files=$(ls-files Python $@)
bin=$(rlocation {{ruff}})
if [ -n "$files" ] && [ -n "$bin" ]; then
echo "Formatting Python with ruff..."
echo "Formatting Python with Ruff..."
echo "$files" | tr \\n \\0 | xargs -0 $bin $ruffmode
fi

Expand Down
24 changes: 15 additions & 9 deletions format/private/formatter_binary.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
load("@aspect_bazel_lib//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path")

# Per the formatter design, each language can only have a single formatter binary
_TOOLS = {
TOOLS = {
"javascript": "prettier",
"markdown": "prettier-md",
"python": "ruff",
Expand All @@ -22,17 +22,23 @@ _TOOLS = {
}

def _formatter_binary_impl(ctx):
# We need to fill in the rlocation paths in the shell script
substitutions = {
"{{BASH_RLOCATION_FUNCTION}}": BASH_RLOCATION_FUNCTION,
"{{fix_target}}": str(ctx.label),
}
tools = {v: getattr(ctx.attr, k) for k, v in _TOOLS.items()}
substitutions = {}
tools = {v: getattr(ctx.attr, k) for k, v in TOOLS.items()}
for tool, attr in tools.items():
if attr:
substitutions["{{%s}}" % tool] = to_rlocation_path(ctx, attr.files_to_run.executable)
if len(substitutions) == 0:
fail("multi_formatter_binary should have at least one language attribute set to a formatter tool")

substitutions.update({
# We need to fill in the rlocation paths in the shell script
"{{BASH_RLOCATION_FUNCTION}}": BASH_RLOCATION_FUNCTION,
# Support helpful error reporting
"{{fix_target}}": str(ctx.label),
})

bin = ctx.actions.declare_file("format.sh")
# Uniquely named output file allowing more than one formatter in a package
bin = ctx.actions.declare_file("_{}.fmt.sh".format(ctx.label.name))
ctx.actions.expand_template(
template = ctx.file._bin,
output = bin,
Expand Down Expand Up @@ -66,7 +72,7 @@ formatter_binary_lib = struct(
implementation = _formatter_binary_impl,
attrs = dict({
k: attr.label(doc = "a binary target that runs {} (or another tool with compatible CLI arguments)".format(v), executable = True, cfg = "exec", allow_files = True)
for k, v in _TOOLS.items()
for k, v in TOOLS.items()
}, **{
"_bin": attr.label(default = "//format/private:format.sh", allow_single_file = True),
"_runfiles_lib": attr.label(default = "@bazel_tools//tools/bash/runfiles", allow_single_file = True),
Expand Down
104 changes: 104 additions & 0 deletions format/test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("//format:defs.bzl", "multi_formatter_binary")
load("//format/private:formatter_binary.bzl", "TOOLS")

# Avoid depending on a bunch of actual tools in the root module.
# That's the job of the example/ submodule.
# Instead, just provide "recording mock" for each formatter we support.
[
write_file(
name = "mock_{}_sh".format(t),
out = "mock_{}.sh".format(t),
content = [
"#!/usr/bin/env bash",
"echo + {} $*".format(t),
],
)
for t in TOOLS.values()
]

[
sh_binary(
name = "mock_" + t,
srcs = ["mock_{}.sh".format(t)],
)
for t in TOOLS.values()
]

# Make a separate formatter binary to test each language in isolation.
# Users should NOT do it this way!
multi_formatter_binary(
name = "format_javascript",
javascript = ":mock_prettier.sh",
)

multi_formatter_binary(
name = "format_starlark",
starlark = ":mock_buildifier.sh",
)

multi_formatter_binary(
name = "format_markdown",
markdown = ":mock_prettier.sh",
)

multi_formatter_binary(
name = "format_sql",
sql = ":mock_prettier.sh",
)

multi_formatter_binary(
name = "format_python",
python = ":mock_ruff.sh",
)

multi_formatter_binary(
name = "format_hcl",
# TODO: this attribute should be renamed to hcl
terraform = ":mock_terraform-fmt.sh",
)

multi_formatter_binary(
name = "format_jsonnet",
jsonnet = ":mock_jsonnetfmt.sh",
)

multi_formatter_binary(
name = "format_java",
java = ":mock_java-format.sh",
)

multi_formatter_binary(
name = "format_kotlin",
kotlin = ":mock_ktfmt.sh",
)

multi_formatter_binary(
name = "format_scala",
scala = ":mock_scalafmt.sh",
)

multi_formatter_binary(
name = "format_go",
go = ":mock_gofmt.sh",
)

multi_formatter_binary(
name = "format_cc",
cc = ":mock_clang-format.sh",
)

multi_formatter_binary(
name = "format_sh",
sh = ":mock_shfmt.sh",
)

multi_formatter_binary(
name = "format_swift",
swift = ":mock_swiftformat.sh",
)

multi_formatter_binary(
name = "format_protobuf",
protobuf = ":mock_buf.sh",
)
149 changes: 149 additions & 0 deletions format/test/format_test.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Simple test fixture that uses this "real" git repository.
# Ideally we would create self-contained "system under test" for each test case.
# That would let us test more scenarios with git, like deleted files.
bats_load_library "bats-support"
bats_load_library "bats-assert"

# No arguments: will use git ls-files
@test "should run prettier on javascript using git ls-files" {
run bazel run //format/test:format_javascript
assert_success

assert_output --partial "Formatting JavaScript with Prettier..."
assert_output --partial "+ prettier --write example/.eslintrc.cjs"
assert_output --partial "Formatting TypeScript with Prettier..."
assert_output --partial "+ prettier --write example/src/file.ts example/test/no_violations.ts"
assert_output --partial "Formatting TSX with Prettier..."
assert_output --partial "+ prettier --write example/src/hello.tsx"
assert_output --partial "Formatting JSON with Prettier..."
assert_output --partial "+ prettier --write renovate.json"
assert_output --partial "Formatting CSS with Prettier..."
assert_output --partial "+ prettier --write example/src/hello.css"
assert_output --partial "Formatting HTML with Prettier..."
assert_output --partial "+ prettier --write example/src/index.html"
}

# File arguments: will filter with find
@test "should run prettier on javascript using find" {
run bazel run //format/test:format_javascript README.md example/.eslintrc.cjs
assert_success

assert_output --partial "Formatting JavaScript with Prettier..."
refute_output --partial "Formatting TypeScript with Prettier..."
}

@test "should run buildozer on starlark" {
run bazel run //format/test:format_starlark
assert_success

assert_output --partial "Formatting Starlark with Buildifier..."
assert_output --partial "+ buildifier -mode=fix BUILD.bazel"
# FIXME(#122): this was broken by #105
# assert_output --partial "format/private/BUILD.bazel"
}

@test "should run prettier on Markdown" {
run bazel run //format/test:format_markdown
assert_success

assert_output --partial "Formatting Markdown with Prettier..."
assert_output --partial "+ prettier --write CONTRIBUTING.md README.md"
}

@test "should run prettier on SQL" {
run bazel run //format/test:format_sql
assert_success

assert_output --partial "Formatting SQL with Prettier..."
assert_output --partial "+ prettier --write example/src/hello.sql"
}

@test "should run ruff on Python" {
run bazel run //format/test:format_python
assert_success

assert_output --partial "Formatting Python with Ruff..."
assert_output --partial "+ ruff format --force-exclude example/src/subdir/unused_import.py"
}

@test "should run terraform fmt on HCL" {
run bazel run //format/test:format_hcl
assert_success

assert_output --partial "Formatting Hashicorp Config Language with terraform fmt..."
assert_output --partial "+ terraform-fmt fmt example/src/hello.tf"
}

@test "should run jsonnet-fmt on Jsonnet" {
run bazel run //format/test:format_jsonnet
assert_success

assert_output --partial "Formatting Jsonnet with jsonnetfmt..."
assert_output --partial "+ jsonnetfmt --in-place example/src/hello.jsonnet example/src/hello.libsonnet"
}

@test "should run java-format on Java" {
run bazel run //format/test:format_java
assert_success

assert_output --partial "Formatting Java with java-format..."
assert_output --partial "+ java-format --replace example/src/Foo.java"
}

@test "should run ktfmt on Kotlin" {
run bazel run //format/test:format_kotlin
assert_success

assert_output --partial "Formatting Kotlin with ktfmt..."
assert_output --partial "+ ktfmt example/src/hello.kt"
}

@test "should run scalafmt on Scala" {
run bazel run //format/test:format_scala
assert_success

assert_output --partial "Formatting Scala with scalafmt..."
assert_output --partial "+ scalafmt example/src/hello.scala"
}

@test "should run gofmt on Go" {
run bazel run //format/test:format_go
assert_success

assert_output --partial "Formatting Go with gofmt..."
assert_output --partial "+ gofmt -w example/src/hello.go"
}

@test "should run clang-format on C++" {
run bazel run //format/test:format_cc
assert_success

assert_output --partial "Formatting C/C++ with clang-format..."
assert_output --partial "+ clang-format -style=file --fallback-style=none -i example/src/hello.cpp"
}

@test "should run shfmt on Shell" {
run bazel run //format/test:format_sh
assert_success

assert_output --partial "Formatting Shell with shfmt..."
assert_output --partial "+ shfmt -w .github/workflows/release_prep.sh"
}

@test "should run swiftformat on Swift" {
run bazel run //format/test:format_swift
assert_success

# The real swiftformat prints the "Formatting..." output so we don't
assert_output --partial "+ swiftformat example/src/hello.swift"
}

@test "should run buf on Protobuf" {
run bazel run //format/test:format_protobuf
assert_success

assert_output --partial "Formatting Protobuf with buf..."
# Buf only formats one file at a time
assert_output --partial "+ buf format -w example/src/file.proto"
assert_output --partial "+ buf format -w example/src/unused.proto"
}
Loading