Skip to content

Commit 15fcab9

Browse files
authored
ci: add test coverage for format binary (#126)
* ci: add test coverage for format binary This is a beginning, if the pattern works I'll apply it to remaining languages. Should be able to catch most regressions this way. * chore: add test coverage for all remaining languages
1 parent 5760fcb commit 15fcab9

File tree

7 files changed

+285
-12
lines changed

7 files changed

+285
-12
lines changed

.github/workflows/ci.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
support-version: "0.3.0"
3939
assert-path: /usr/lib/bats/bats-assert
4040
assert-version: "2.1.0"
41-
- name: Integration test
41+
- name: "Integration test: example"
4242
working-directory: example
4343
run: bats ./test
44+
- name: "Integration test: format"
45+
working-directory: format
46+
run: bats ./test

example/src/hello.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.h1 {
2+
}

example/src/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<!DOCTYPE html>
2+
<html></html>

format/private/format.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ if [ -n "$files" ] && [ -n "$bin" ]; then
140140
echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode
141141
fi
142142

143+
files=$(ls-files JSON $@)
144+
bin=$(rlocation {{prettier}})
145+
if [ -n "$files" ] && [ -n "$bin" ]; then
146+
echo "Formatting JSON with Prettier..."
147+
echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode
148+
fi
149+
143150
files=$(ls-files JavaScript $@)
144151
bin=$(rlocation {{prettier}})
145152
if [ -n "$files" ] && [ -n "$bin" ]; then
@@ -178,14 +185,14 @@ fi
178185
files=$(ls-files SQL $@)
179186
bin=$(rlocation {{prettier-sql}})
180187
if [ -n "$files" ] && [ -n "$bin" ]; then
181-
echo "Running SQL with Prettier..."
188+
echo "Formatting SQL with Prettier..."
182189
echo "$files" | tr \\n \\0 | xargs -0 $bin $prettiermode
183190
fi
184191

185192
files=$(ls-files Python $@)
186193
bin=$(rlocation {{ruff}})
187194
if [ -n "$files" ] && [ -n "$bin" ]; then
188-
echo "Formatting Python with ruff..."
195+
echo "Formatting Python with Ruff..."
189196
echo "$files" | tr \\n \\0 | xargs -0 $bin $ruffmode
190197
fi
191198

format/private/formatter_binary.bzl

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
load("@aspect_bazel_lib//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path")
44

55
# Per the formatter design, each language can only have a single formatter binary
6-
_TOOLS = {
6+
TOOLS = {
77
"javascript": "prettier",
88
"markdown": "prettier-md",
99
"python": "ruff",
@@ -22,17 +22,23 @@ _TOOLS = {
2222
}
2323

2424
def _formatter_binary_impl(ctx):
25-
# We need to fill in the rlocation paths in the shell script
26-
substitutions = {
27-
"{{BASH_RLOCATION_FUNCTION}}": BASH_RLOCATION_FUNCTION,
28-
"{{fix_target}}": str(ctx.label),
29-
}
30-
tools = {v: getattr(ctx.attr, k) for k, v in _TOOLS.items()}
25+
substitutions = {}
26+
tools = {v: getattr(ctx.attr, k) for k, v in TOOLS.items()}
3127
for tool, attr in tools.items():
3228
if attr:
3329
substitutions["{{%s}}" % tool] = to_rlocation_path(ctx, attr.files_to_run.executable)
30+
if len(substitutions) == 0:
31+
fail("multi_formatter_binary should have at least one language attribute set to a formatter tool")
32+
33+
substitutions.update({
34+
# We need to fill in the rlocation paths in the shell script
35+
"{{BASH_RLOCATION_FUNCTION}}": BASH_RLOCATION_FUNCTION,
36+
# Support helpful error reporting
37+
"{{fix_target}}": str(ctx.label),
38+
})
3439

35-
bin = ctx.actions.declare_file("format.sh")
40+
# Uniquely named output file allowing more than one formatter in a package
41+
bin = ctx.actions.declare_file("_{}.fmt.sh".format(ctx.label.name))
3642
ctx.actions.expand_template(
3743
template = ctx.file._bin,
3844
output = bin,
@@ -66,7 +72,7 @@ formatter_binary_lib = struct(
6672
implementation = _formatter_binary_impl,
6773
attrs = dict({
6874
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)
69-
for k, v in _TOOLS.items()
75+
for k, v in TOOLS.items()
7076
}, **{
7177
"_bin": attr.label(default = "//format/private:format.sh", allow_single_file = True),
7278
"_runfiles_lib": attr.label(default = "@bazel_tools//tools/bash/runfiles", allow_single_file = True),

format/test/BUILD.bazel

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
2+
load("//format:defs.bzl", "multi_formatter_binary")
3+
load("//format/private:formatter_binary.bzl", "TOOLS")
4+
5+
# Avoid depending on a bunch of actual tools in the root module.
6+
# That's the job of the example/ submodule.
7+
# Instead, just provide "recording mock" for each formatter we support.
8+
[
9+
write_file(
10+
name = "mock_{}_sh".format(t),
11+
out = "mock_{}.sh".format(t),
12+
content = [
13+
"#!/usr/bin/env bash",
14+
"echo + {} $*".format(t),
15+
],
16+
)
17+
for t in TOOLS.values()
18+
]
19+
20+
[
21+
sh_binary(
22+
name = "mock_" + t,
23+
srcs = ["mock_{}.sh".format(t)],
24+
)
25+
for t in TOOLS.values()
26+
]
27+
28+
# Make a separate formatter binary to test each language in isolation.
29+
# Users should NOT do it this way!
30+
multi_formatter_binary(
31+
name = "format_javascript",
32+
javascript = ":mock_prettier.sh",
33+
)
34+
35+
multi_formatter_binary(
36+
name = "format_starlark",
37+
starlark = ":mock_buildifier.sh",
38+
)
39+
40+
multi_formatter_binary(
41+
name = "format_markdown",
42+
markdown = ":mock_prettier.sh",
43+
)
44+
45+
multi_formatter_binary(
46+
name = "format_sql",
47+
sql = ":mock_prettier.sh",
48+
)
49+
50+
multi_formatter_binary(
51+
name = "format_python",
52+
python = ":mock_ruff.sh",
53+
)
54+
55+
multi_formatter_binary(
56+
name = "format_hcl",
57+
# TODO: this attribute should be renamed to hcl
58+
terraform = ":mock_terraform-fmt.sh",
59+
)
60+
61+
multi_formatter_binary(
62+
name = "format_jsonnet",
63+
jsonnet = ":mock_jsonnetfmt.sh",
64+
)
65+
66+
multi_formatter_binary(
67+
name = "format_java",
68+
java = ":mock_java-format.sh",
69+
)
70+
71+
multi_formatter_binary(
72+
name = "format_kotlin",
73+
kotlin = ":mock_ktfmt.sh",
74+
)
75+
76+
multi_formatter_binary(
77+
name = "format_scala",
78+
scala = ":mock_scalafmt.sh",
79+
)
80+
81+
multi_formatter_binary(
82+
name = "format_go",
83+
go = ":mock_gofmt.sh",
84+
)
85+
86+
multi_formatter_binary(
87+
name = "format_cc",
88+
cc = ":mock_clang-format.sh",
89+
)
90+
91+
multi_formatter_binary(
92+
name = "format_sh",
93+
sh = ":mock_shfmt.sh",
94+
)
95+
96+
multi_formatter_binary(
97+
name = "format_swift",
98+
swift = ":mock_swiftformat.sh",
99+
)
100+
101+
multi_formatter_binary(
102+
name = "format_protobuf",
103+
protobuf = ":mock_buf.sh",
104+
)

format/test/format_test.bats

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Simple test fixture that uses this "real" git repository.
2+
# Ideally we would create self-contained "system under test" for each test case.
3+
# That would let us test more scenarios with git, like deleted files.
4+
bats_load_library "bats-support"
5+
bats_load_library "bats-assert"
6+
7+
# No arguments: will use git ls-files
8+
@test "should run prettier on javascript using git ls-files" {
9+
run bazel run //format/test:format_javascript
10+
assert_success
11+
12+
assert_output --partial "Formatting JavaScript with Prettier..."
13+
assert_output --partial "+ prettier --write example/.eslintrc.cjs"
14+
assert_output --partial "Formatting TypeScript with Prettier..."
15+
assert_output --partial "+ prettier --write example/src/file.ts example/test/no_violations.ts"
16+
assert_output --partial "Formatting TSX with Prettier..."
17+
assert_output --partial "+ prettier --write example/src/hello.tsx"
18+
assert_output --partial "Formatting JSON with Prettier..."
19+
assert_output --partial "+ prettier --write renovate.json"
20+
assert_output --partial "Formatting CSS with Prettier..."
21+
assert_output --partial "+ prettier --write example/src/hello.css"
22+
assert_output --partial "Formatting HTML with Prettier..."
23+
assert_output --partial "+ prettier --write example/src/index.html"
24+
}
25+
26+
# File arguments: will filter with find
27+
@test "should run prettier on javascript using find" {
28+
run bazel run //format/test:format_javascript README.md example/.eslintrc.cjs
29+
assert_success
30+
31+
assert_output --partial "Formatting JavaScript with Prettier..."
32+
refute_output --partial "Formatting TypeScript with Prettier..."
33+
}
34+
35+
@test "should run buildozer on starlark" {
36+
run bazel run //format/test:format_starlark
37+
assert_success
38+
39+
assert_output --partial "Formatting Starlark with Buildifier..."
40+
assert_output --partial "+ buildifier -mode=fix BUILD.bazel"
41+
# FIXME(#122): this was broken by #105
42+
# assert_output --partial "format/private/BUILD.bazel"
43+
}
44+
45+
@test "should run prettier on Markdown" {
46+
run bazel run //format/test:format_markdown
47+
assert_success
48+
49+
assert_output --partial "Formatting Markdown with Prettier..."
50+
assert_output --partial "+ prettier --write CONTRIBUTING.md README.md"
51+
}
52+
53+
@test "should run prettier on SQL" {
54+
run bazel run //format/test:format_sql
55+
assert_success
56+
57+
assert_output --partial "Formatting SQL with Prettier..."
58+
assert_output --partial "+ prettier --write example/src/hello.sql"
59+
}
60+
61+
@test "should run ruff on Python" {
62+
run bazel run //format/test:format_python
63+
assert_success
64+
65+
assert_output --partial "Formatting Python with Ruff..."
66+
assert_output --partial "+ ruff format --force-exclude example/src/subdir/unused_import.py"
67+
}
68+
69+
@test "should run terraform fmt on HCL" {
70+
run bazel run //format/test:format_hcl
71+
assert_success
72+
73+
assert_output --partial "Formatting Hashicorp Config Language with terraform fmt..."
74+
assert_output --partial "+ terraform-fmt fmt example/src/hello.tf"
75+
}
76+
77+
@test "should run jsonnet-fmt on Jsonnet" {
78+
run bazel run //format/test:format_jsonnet
79+
assert_success
80+
81+
assert_output --partial "Formatting Jsonnet with jsonnetfmt..."
82+
assert_output --partial "+ jsonnetfmt --in-place example/src/hello.jsonnet example/src/hello.libsonnet"
83+
}
84+
85+
@test "should run java-format on Java" {
86+
run bazel run //format/test:format_java
87+
assert_success
88+
89+
assert_output --partial "Formatting Java with java-format..."
90+
assert_output --partial "+ java-format --replace example/src/Foo.java"
91+
}
92+
93+
@test "should run ktfmt on Kotlin" {
94+
run bazel run //format/test:format_kotlin
95+
assert_success
96+
97+
assert_output --partial "Formatting Kotlin with ktfmt..."
98+
assert_output --partial "+ ktfmt example/src/hello.kt"
99+
}
100+
101+
@test "should run scalafmt on Scala" {
102+
run bazel run //format/test:format_scala
103+
assert_success
104+
105+
assert_output --partial "Formatting Scala with scalafmt..."
106+
assert_output --partial "+ scalafmt example/src/hello.scala"
107+
}
108+
109+
@test "should run gofmt on Go" {
110+
run bazel run //format/test:format_go
111+
assert_success
112+
113+
assert_output --partial "Formatting Go with gofmt..."
114+
assert_output --partial "+ gofmt -w example/src/hello.go"
115+
}
116+
117+
@test "should run clang-format on C++" {
118+
run bazel run //format/test:format_cc
119+
assert_success
120+
121+
assert_output --partial "Formatting C/C++ with clang-format..."
122+
assert_output --partial "+ clang-format -style=file --fallback-style=none -i example/src/hello.cpp"
123+
}
124+
125+
@test "should run shfmt on Shell" {
126+
run bazel run //format/test:format_sh
127+
assert_success
128+
129+
assert_output --partial "Formatting Shell with shfmt..."
130+
assert_output --partial "+ shfmt -w .github/workflows/release_prep.sh"
131+
}
132+
133+
@test "should run swiftformat on Swift" {
134+
run bazel run //format/test:format_swift
135+
assert_success
136+
137+
# The real swiftformat prints the "Formatting..." output so we don't
138+
assert_output --partial "+ swiftformat example/src/hello.swift"
139+
}
140+
141+
@test "should run buf on Protobuf" {
142+
run bazel run //format/test:format_protobuf
143+
assert_success
144+
145+
assert_output --partial "Formatting Protobuf with buf..."
146+
# Buf only formats one file at a time
147+
assert_output --partial "+ buf format -w example/src/file.proto"
148+
assert_output --partial "+ buf format -w example/src/unused.proto"
149+
}

0 commit comments

Comments
 (0)