diff --git a/gazelle/generate.go b/gazelle/generate.go index 077acb821a..c7b0709687 100644 --- a/gazelle/generate.go +++ b/gazelle/generate.go @@ -26,6 +26,8 @@ const ( pyBinaryEntrypointFilename = "__main__.py" pyTestEntrypointFilename = "__test__.py" pyTestEntrypointTargetname = "__test__" + conftestFilename = "conftest.py" + conftestTargetname = "conftest" ) var ( @@ -71,6 +73,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes // be generated for this package or not. hasPyTestFile := false hasPyTestTarget := false + hasConftestFile := false for _, f := range args.RegularFiles { if cfg.IgnoresFile(filepath.Base(f)) { @@ -81,6 +84,8 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes hasPyBinary = true } else if !hasPyTestFile && f == pyTestEntrypointFilename { hasPyTestFile = true + } else if f == conftestFilename { + hasConftestFile = true } else if strings.HasSuffix(f, "_test.py") || (strings.HasPrefix(f, "test_") && ext == ".py") { pyTestFilenames.Add(f) } else if ext == ".py" { @@ -196,10 +201,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes pyLibraryTargetName := cfg.RenderLibraryName(packageName) - // Check if a target with the same name we are generating alredy exists, - // and if it is of a different kind from the one we are generating. If - // so, we have to throw an error since Gazelle won't generate it - // correctly. + // Check if a target with the same name we are generating already + // exists, and if it is of a different kind from the one we are + // generating. If so, we have to throw an error since Gazelle won't + // generate it correctly. if args.File != nil { for _, t := range args.File.Rules { if t.Name() == pyLibraryTargetName && t.Kind() != pyLibraryKind { @@ -233,10 +238,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes pyBinaryTargetName := cfg.RenderBinaryName(packageName) - // Check if a target with the same name we are generating alredy exists, - // and if it is of a different kind from the one we are generating. If - // so, we have to throw an error since Gazelle won't generate it - // correctly. + // Check if a target with the same name we are generating already + // exists, and if it is of a different kind from the one we are + // generating. If so, we have to throw an error since Gazelle won't + // generate it correctly. if args.File != nil { for _, t := range args.File.Rules { if t.Name() == pyBinaryTargetName && t.Kind() != pyBinaryKind { @@ -267,6 +272,43 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes result.Imports = append(result.Imports, pyBinary.PrivateAttr(config.GazelleImportsKey)) } + var conftest *rule.Rule + if hasConftestFile { + deps, err := parser.parseSingle(conftestFilename) + if err != nil { + log.Fatalf("ERROR: %v\n", err) + } + + // Check if a target with the same name we are generating already + // exists, and if it is of a different kind from the one we are + // generating. If so, we have to throw an error since Gazelle won't + // generate it correctly. + if args.File != nil { + for _, t := range args.File.Rules { + if t.Name() == conftestTargetname && t.Kind() != pyLibraryKind { + fqTarget := label.New("", args.Rel, conftestTargetname) + err := fmt.Errorf("failed to generate target %q of kind %q: "+ + "a target of kind %q with the same name already exists.", + fqTarget.String(), pyLibraryKind, t.Kind()) + collisionErrors.Add(err) + } + } + } + + conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel). + setUUID(uuid.Must(uuid.NewUUID()).String()). + addSrc(conftestFilename). + addModuleDependencies(deps). + addVisibility(visibility). + setTestonly(). + generateImportsAttribute() + + conftest = conftestTarget.build() + + result.Gen = append(result.Gen, conftest) + result.Imports = append(result.Imports, conftest.PrivateAttr(config.GazelleImportsKey)) + } + if hasPyTestFile || hasPyTestTarget { if hasPyTestFile { // Only add the pyTestEntrypointFilename to the pyTestFilenames if @@ -280,10 +322,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes pyTestTargetName := cfg.RenderTestName(packageName) - // Check if a target with the same name we are generating alredy exists, - // and if it is of a different kind from the one we are generating. If - // so, we have to throw an error since Gazelle won't generate it - // correctly. + // Check if a target with the same name we are generating already + // exists, and if it is of a different kind from the one we are + // generating. If so, we have to throw an error since Gazelle won't + // generate it correctly. if args.File != nil { for _, t := range args.File.Rules { if t.Name() == pyTestTargetName && t.Kind() != pyTestKind { @@ -317,6 +359,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes pyTestTarget.addModuleDependency(module{Name: pyLibrary.PrivateAttr(uuidKey).(string)}) } + if conftest != nil { + pyTestTarget.addModuleDependency(module{Name: conftest.PrivateAttr(uuidKey).(string)}) + } + pyTest := pyTestTarget.build() result.Gen = append(result.Gen, pyTest) diff --git a/gazelle/target.go b/gazelle/target.go index 2b260679b6..eef3aedd8d 100644 --- a/gazelle/target.go +++ b/gazelle/target.go @@ -22,6 +22,7 @@ type targetBuilder struct { visibility *treeset.Set main *string imports []string + testonly bool } // newTargetBuilder constructs a new targetBuilder. @@ -96,6 +97,12 @@ func (t *targetBuilder) setMain(main string) *targetBuilder { return t } +// setTestonly sets the testonly attribute to true. +func (t *targetBuilder) setTestonly() *targetBuilder { + t.testonly = true + return t +} + // generateImportsAttribute generates the imports attribute. // These are a list of import directories to be added to the PYTHONPATH. In our // 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 { if !t.deps.Empty() { r.SetPrivateAttr(config.GazelleImportsKey, t.deps) } + if t.testonly { + r.SetAttr("testonly", true) + } r.SetPrivateAttr(resolvedDepsKey, t.resolvedDeps) return r } diff --git a/gazelle/testdata/simple_test_with_conftest/BUILD.in b/gazelle/testdata/simple_test_with_conftest/BUILD.in new file mode 100644 index 0000000000..3f2beb3147 --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/BUILD.in @@ -0,0 +1 @@ +load("@rules_python//python:defs.bzl", "py_library") diff --git a/gazelle/testdata/simple_test_with_conftest/BUILD.out b/gazelle/testdata/simple_test_with_conftest/BUILD.out new file mode 100644 index 0000000000..18079bf2f4 --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/BUILD.out @@ -0,0 +1,27 @@ +load("@rules_python//python:defs.bzl", "py_library", "py_test") + +py_library( + name = "simple_test_with_conftest", + srcs = [ + "__init__.py", + "foo.py", + ], + visibility = ["//:__subpackages__"], +) + +py_library( + name = "conftest", + testonly = True, + srcs = ["conftest.py"], + visibility = ["//:__subpackages__"], +) + +py_test( + name = "simple_test_with_conftest_test", + srcs = ["__test__.py"], + main = "__test__.py", + deps = [ + ":conftest", + ":simple_test_with_conftest", + ], +) diff --git a/gazelle/testdata/simple_test_with_conftest/README.md b/gazelle/testdata/simple_test_with_conftest/README.md new file mode 100644 index 0000000000..0ff245f808 --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/README.md @@ -0,0 +1,4 @@ +# Simple test with conftest.py + +This test case asserts that a simple `py_test` is generated as expected when a +`conftest.py` is present. diff --git a/gazelle/testdata/simple_test_with_conftest/WORKSPACE b/gazelle/testdata/simple_test_with_conftest/WORKSPACE new file mode 100644 index 0000000000..faff6af87a --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/WORKSPACE @@ -0,0 +1 @@ +# This is a Bazel workspace for the Gazelle test data. diff --git a/gazelle/testdata/simple_test_with_conftest/__init__.py b/gazelle/testdata/simple_test_with_conftest/__init__.py new file mode 100644 index 0000000000..6a49193fe4 --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/__init__.py @@ -0,0 +1,3 @@ +from foo import foo + +_ = foo diff --git a/gazelle/testdata/simple_test_with_conftest/__test__.py b/gazelle/testdata/simple_test_with_conftest/__test__.py new file mode 100644 index 0000000000..d6085a41b4 --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/__test__.py @@ -0,0 +1,12 @@ +import unittest + +from __init__ import foo + + +class FooTest(unittest.TestCase): + def test_foo(self): + self.assertEqual("foo", foo()) + + +if __name__ == "__main__": + unittest.main() diff --git a/gazelle/testdata/simple_test_with_conftest/conftest.py b/gazelle/testdata/simple_test_with_conftest/conftest.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/testdata/simple_test_with_conftest/foo.py b/gazelle/testdata/simple_test_with_conftest/foo.py new file mode 100644 index 0000000000..cf68624419 --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/foo.py @@ -0,0 +1,2 @@ +def foo(): + return "foo" diff --git a/gazelle/testdata/simple_test_with_conftest/test.yaml b/gazelle/testdata/simple_test_with_conftest/test.yaml new file mode 100644 index 0000000000..36dd656b39 --- /dev/null +++ b/gazelle/testdata/simple_test_with_conftest/test.yaml @@ -0,0 +1,3 @@ +--- +expect: + exit_code: 0