Skip to content

Commit 912a505

Browse files
authored
fix: make conftest.py special with gazelle (#879)
* fix: add conftest.py to py_test generated targets Signed-off-by: Thulio Ferraz Assis <[email protected]> * fix: use separate py_library for conftest.py This allows the conftest.py to be used on sub-directories as pytest would pick them up. Signed-off-by: Thulio Ferraz Assis <[email protected]> * fix: add testonly to conftest py_library Signed-off-by: Thulio Ferraz Assis <[email protected]> * fix: testonly is a boolean, not a string Signed-off-by: Thulio Ferraz Assis <[email protected]> Signed-off-by: Thulio Ferraz Assis <[email protected]>
1 parent 3f0d62d commit 912a505

File tree

11 files changed

+121
-12
lines changed

11 files changed

+121
-12
lines changed

gazelle/generate.go

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const (
2626
pyBinaryEntrypointFilename = "__main__.py"
2727
pyTestEntrypointFilename = "__test__.py"
2828
pyTestEntrypointTargetname = "__test__"
29+
conftestFilename = "conftest.py"
30+
conftestTargetname = "conftest"
2931
)
3032

3133
var (
@@ -71,6 +73,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
7173
// be generated for this package or not.
7274
hasPyTestFile := false
7375
hasPyTestTarget := false
76+
hasConftestFile := false
7477

7578
for _, f := range args.RegularFiles {
7679
if cfg.IgnoresFile(filepath.Base(f)) {
@@ -81,6 +84,8 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
8184
hasPyBinary = true
8285
} else if !hasPyTestFile && f == pyTestEntrypointFilename {
8386
hasPyTestFile = true
87+
} else if f == conftestFilename {
88+
hasConftestFile = true
8489
} else if strings.HasSuffix(f, "_test.py") || (strings.HasPrefix(f, "test_") && ext == ".py") {
8590
pyTestFilenames.Add(f)
8691
} else if ext == ".py" {
@@ -196,10 +201,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
196201

197202
pyLibraryTargetName := cfg.RenderLibraryName(packageName)
198203

199-
// Check if a target with the same name we are generating alredy exists,
200-
// and if it is of a different kind from the one we are generating. If
201-
// so, we have to throw an error since Gazelle won't generate it
202-
// correctly.
204+
// Check if a target with the same name we are generating already
205+
// exists, and if it is of a different kind from the one we are
206+
// generating. If so, we have to throw an error since Gazelle won't
207+
// generate it correctly.
203208
if args.File != nil {
204209
for _, t := range args.File.Rules {
205210
if t.Name() == pyLibraryTargetName && t.Kind() != pyLibraryKind {
@@ -233,10 +238,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
233238

234239
pyBinaryTargetName := cfg.RenderBinaryName(packageName)
235240

236-
// Check if a target with the same name we are generating alredy exists,
237-
// and if it is of a different kind from the one we are generating. If
238-
// so, we have to throw an error since Gazelle won't generate it
239-
// correctly.
241+
// Check if a target with the same name we are generating already
242+
// exists, and if it is of a different kind from the one we are
243+
// generating. If so, we have to throw an error since Gazelle won't
244+
// generate it correctly.
240245
if args.File != nil {
241246
for _, t := range args.File.Rules {
242247
if t.Name() == pyBinaryTargetName && t.Kind() != pyBinaryKind {
@@ -267,6 +272,43 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
267272
result.Imports = append(result.Imports, pyBinary.PrivateAttr(config.GazelleImportsKey))
268273
}
269274

275+
var conftest *rule.Rule
276+
if hasConftestFile {
277+
deps, err := parser.parseSingle(conftestFilename)
278+
if err != nil {
279+
log.Fatalf("ERROR: %v\n", err)
280+
}
281+
282+
// Check if a target with the same name we are generating already
283+
// exists, and if it is of a different kind from the one we are
284+
// generating. If so, we have to throw an error since Gazelle won't
285+
// generate it correctly.
286+
if args.File != nil {
287+
for _, t := range args.File.Rules {
288+
if t.Name() == conftestTargetname && t.Kind() != pyLibraryKind {
289+
fqTarget := label.New("", args.Rel, conftestTargetname)
290+
err := fmt.Errorf("failed to generate target %q of kind %q: "+
291+
"a target of kind %q with the same name already exists.",
292+
fqTarget.String(), pyLibraryKind, t.Kind())
293+
collisionErrors.Add(err)
294+
}
295+
}
296+
}
297+
298+
conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel).
299+
setUUID(uuid.Must(uuid.NewUUID()).String()).
300+
addSrc(conftestFilename).
301+
addModuleDependencies(deps).
302+
addVisibility(visibility).
303+
setTestonly().
304+
generateImportsAttribute()
305+
306+
conftest = conftestTarget.build()
307+
308+
result.Gen = append(result.Gen, conftest)
309+
result.Imports = append(result.Imports, conftest.PrivateAttr(config.GazelleImportsKey))
310+
}
311+
270312
if hasPyTestFile || hasPyTestTarget {
271313
if hasPyTestFile {
272314
// Only add the pyTestEntrypointFilename to the pyTestFilenames if
@@ -280,10 +322,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
280322

281323
pyTestTargetName := cfg.RenderTestName(packageName)
282324

283-
// Check if a target with the same name we are generating alredy exists,
284-
// and if it is of a different kind from the one we are generating. If
285-
// so, we have to throw an error since Gazelle won't generate it
286-
// correctly.
325+
// Check if a target with the same name we are generating already
326+
// exists, and if it is of a different kind from the one we are
327+
// generating. If so, we have to throw an error since Gazelle won't
328+
// generate it correctly.
287329
if args.File != nil {
288330
for _, t := range args.File.Rules {
289331
if t.Name() == pyTestTargetName && t.Kind() != pyTestKind {
@@ -317,6 +359,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
317359
pyTestTarget.addModuleDependency(module{Name: pyLibrary.PrivateAttr(uuidKey).(string)})
318360
}
319361

362+
if conftest != nil {
363+
pyTestTarget.addModuleDependency(module{Name: conftest.PrivateAttr(uuidKey).(string)})
364+
}
365+
320366
pyTest := pyTestTarget.build()
321367

322368
result.Gen = append(result.Gen, pyTest)

gazelle/target.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type targetBuilder struct {
2222
visibility *treeset.Set
2323
main *string
2424
imports []string
25+
testonly bool
2526
}
2627

2728
// newTargetBuilder constructs a new targetBuilder.
@@ -96,6 +97,12 @@ func (t *targetBuilder) setMain(main string) *targetBuilder {
9697
return t
9798
}
9899

100+
// setTestonly sets the testonly attribute to true.
101+
func (t *targetBuilder) setTestonly() *targetBuilder {
102+
t.testonly = true
103+
return t
104+
}
105+
99106
// generateImportsAttribute generates the imports attribute.
100107
// These are a list of import directories to be added to the PYTHONPATH. In our
101108
// case, the value we add is on Bazel sub-packages to be able to perform imports
@@ -131,6 +138,9 @@ func (t *targetBuilder) build() *rule.Rule {
131138
if !t.deps.Empty() {
132139
r.SetPrivateAttr(config.GazelleImportsKey, t.deps)
133140
}
141+
if t.testonly {
142+
r.SetAttr("testonly", true)
143+
}
134144
r.SetPrivateAttr(resolvedDepsKey, t.resolvedDeps)
135145
return r
136146
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
load("@rules_python//python:defs.bzl", "py_library")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("@rules_python//python:defs.bzl", "py_library", "py_test")
2+
3+
py_library(
4+
name = "simple_test_with_conftest",
5+
srcs = [
6+
"__init__.py",
7+
"foo.py",
8+
],
9+
visibility = ["//:__subpackages__"],
10+
)
11+
12+
py_library(
13+
name = "conftest",
14+
testonly = True,
15+
srcs = ["conftest.py"],
16+
visibility = ["//:__subpackages__"],
17+
)
18+
19+
py_test(
20+
name = "simple_test_with_conftest_test",
21+
srcs = ["__test__.py"],
22+
main = "__test__.py",
23+
deps = [
24+
":conftest",
25+
":simple_test_with_conftest",
26+
],
27+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Simple test with conftest.py
2+
3+
This test case asserts that a simple `py_test` is generated as expected when a
4+
`conftest.py` is present.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This is a Bazel workspace for the Gazelle test data.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from foo import foo
2+
3+
_ = foo
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import unittest
2+
3+
from __init__ import foo
4+
5+
6+
class FooTest(unittest.TestCase):
7+
def test_foo(self):
8+
self.assertEqual("foo", foo())
9+
10+
11+
if __name__ == "__main__":
12+
unittest.main()

gazelle/testdata/simple_test_with_conftest/conftest.py

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def foo():
2+
return "foo"

0 commit comments

Comments
 (0)