From 58b502ae64ba8eadd90b3e7f1b0d7f4c9ec4c296 Mon Sep 17 00:00:00 2001 From: Reshmi Date: Wed, 3 Jun 2026 10:23:08 +0530 Subject: [PATCH 1/4] RTECO-1287 jf poetry install --only main captures all dependency groups in build info, ignoring group filters --- buildtools/cli.go | 2 +- go.mod | 2 +- go.sum | 4 +- utils/buildinfo/buildinfo.go | 100 ++++++++++++++++++++++++++++++++--- 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/buildtools/cli.go b/buildtools/cli.go index eaaa9ba2d..024066a74 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -2130,7 +2130,7 @@ func pythonCmd(c *cli.Context, projectType project.ProjectType) error { workingDir, err := os.Getwd() if err != nil { log.Warn("Failed to get working directory, skipping build info collection: " + err.Error()) - } else if err := buildinfo.GetPoetryBuildInfo(workingDir, buildConfiguration, deployerRepo); err != nil { + } else if err := buildinfo.GetPoetryBuildInfo(workingDir, buildConfiguration, deployerRepo, cmdName, poetryArgs); err != nil { log.Warn("Failed to collect Poetry build info: " + err.Error()) } else { buildNumber, err := buildConfiguration.GetBuildNumber() diff --git a/go.mod b/go.mod index d82b8131d..0cb8b2e15 100644 --- a/go.mod +++ b/go.mod @@ -247,7 +247,7 @@ require ( // replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260416054314-f941180fccc3 -// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.13.1-0.20260428071432-1e9d9a1991ad +replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.13.1-0.20260603044611-897a097a7031 // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260518123155-036d9195c4e9 diff --git a/go.sum b/go.sum index 65765bd56..9c80499e1 100644 --- a/go.sum +++ b/go.sum @@ -400,8 +400,8 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jfrog/archiver/v3 v3.6.3 h1:hkAmPjBw393tPmQ07JknLNWFNZjXdy2xFEnOW9wwOxI= github.com/jfrog/archiver/v3 v3.6.3/go.mod h1:5V9l+Fte30Y4qe9dUOAd3yNTf8lmtVNuhKNrvI8PMhg= -github.com/jfrog/build-info-go v1.13.1-0.20260528065004-80409c046540 h1:yJjTgSfmsBx9Q6/iiJxXQ/m0KZfFjNx8nNzaRLCM7z4= -github.com/jfrog/build-info-go v1.13.1-0.20260528065004-80409c046540/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= +github.com/jfrog/build-info-go v1.13.1-0.20260603044611-897a097a7031 h1:/OkgqPsrHP05VWj/o+nygOj1eMxtvq7So7iEtKIUO/o= +github.com/jfrog/build-info-go v1.13.1-0.20260603044611-897a097a7031/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= github.com/jfrog/froggit-go v1.22.0 h1:eeN5F8sOUo+h2cXkzArAu4nvSdjkDTAZtgqwrct70qg= github.com/jfrog/froggit-go v1.22.0/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8= diff --git a/utils/buildinfo/buildinfo.go b/utils/buildinfo/buildinfo.go index a8a501e8d..22e5937b9 100644 --- a/utils/buildinfo/buildinfo.go +++ b/utils/buildinfo/buildinfo.go @@ -1,6 +1,7 @@ package buildinfo import ( + "encoding/json" "fmt" "net/url" "os" @@ -44,7 +45,9 @@ func GetBuildInfoForPackageManager(pkgManager, workingDir string, buildConfigura switch pkgManager { case "poetry": - return GetPoetryBuildInfo(workingDir, buildConfiguration, "") // Empty deployer repo - will use from pyproject.toml + // No cmd/args context available from this entry point — falls back to + // the existing lock-file-driven behaviour (no installed-set filtering). + return GetPoetryBuildInfo(workingDir, buildConfiguration, "", "", nil) // Empty deployer repo - will use from pyproject.toml case "mvn", "maven": // Maven FlexPack is handled directly in jfrog-cli-artifactory Maven command return GetBuildInfoForUploadedArtifacts("", buildConfiguration) @@ -61,8 +64,15 @@ func GetBuildInfoForPackageManager(pkgManager, workingDir string, buildConfigura } } -// GetPoetryBuildInfo collects build info for Poetry projects -func GetPoetryBuildInfo(workingDir string, buildConfiguration *buildUtils.BuildConfiguration, deployerRepo string) error { +// GetPoetryBuildInfo collects build info for Poetry projects. +// +// cmdName and args describe the poetry sub-command that just ran (e.g. "install", +// "--only", "main"). They are used to decide whether to query the active poetry +// venv for the ground-truth installed set — which is how we honor poetry's +// --only/--without/--with flags in build-info, mirroring the proven UV pattern. +// When cmdName is empty (legacy entry points), no ground-truth query is made +// and the existing lock-file-driven behaviour is preserved. +func GetPoetryBuildInfo(workingDir string, buildConfiguration *buildUtils.BuildConfiguration, deployerRepo, cmdName string, args []string) error { log.Debug("Collecting Poetry build info from directory: " + workingDir) log.Debug("Deployer repository: " + deployerRepo) @@ -102,7 +112,17 @@ func GetPoetryBuildInfo(workingDir string, buildConfiguration *buildUtils.BuildC } log.Info(fmt.Sprintf("Using repository for artifacts: %s", artifactRepo)) - err = collectPoetryBuildInfo(workingDir, buildName, buildNumber, serverDetails, repoConfig.TargetRepo(), artifactRepo, buildConfiguration) + // For venv-modifying commands (install/add/remove/update/sync), capture the + // real installed set from poetry so build-info reflects what was actually + // installed — honouring --only/--without/--with without any flag parsing. + // For other commands and on any error this stays nil → legacy behaviour. + var installed map[string]string + _ = args // reserved for future per-arg behaviour; ground truth comes from poetry itself + if poetryModifiesVenv(cmdName) { + installed = poetryInstalledPackages(workingDir) + } + + err = collectPoetryBuildInfo(workingDir, buildName, buildNumber, serverDetails, repoConfig.TargetRepo(), artifactRepo, installed, buildConfiguration) if err != nil { log.Warn("Enhanced Poetry collection failed, falling back to standard method: " + err.Error()) err = saveBuildInfo(serverDetails, artifactRepo, "", buildConfiguration) @@ -359,14 +379,19 @@ func CreateAqlQueryForSearch(repo, file string) string { return fmt.Sprintf(itemsPart, repo, file) } -// collectPoetryBuildInfo collects Poetry dependencies and artifacts for build info -func collectPoetryBuildInfo(workingDir, buildName, buildNumber string, serverDetails *config.ServerDetails, _ string, artifactRepo string, buildConfiguration *buildUtils.BuildConfiguration) error { +// collectPoetryBuildInfo collects Poetry dependencies and artifacts for build info. +// +// installedPackages, when non-nil, is the ground-truth set captured from +// `poetry run pip list`. Only lockfile entries present in this map are included +// in build-info, which is how --only/--without/--with are honoured. +func collectPoetryBuildInfo(workingDir, buildName, buildNumber string, serverDetails *config.ServerDetails, _ string, artifactRepo string, installedPackages map[string]string, buildConfiguration *buildUtils.BuildConfiguration) error { log.Debug("Initializing Poetry dependency collection...") // Create Poetry configuration config := flexpack.PoetryConfig{ WorkingDirectory: workingDir, IncludeDevDependencies: false, // Match standard behavior + InstalledPackages: installedPackages, } // Create Poetry instance @@ -799,3 +824,66 @@ func extractRepoNameFromPypiURL(urlStr string) string { return "" } + +// poetryModifiesVenv reports whether the named poetry sub-command actually +// installs/uninstalls packages into the venv. Only for these commands does +// querying the venv for the installed set make sense — for lock/build/publish +// etc. the venv state is unrelated to the operation. +func poetryModifiesVenv(cmdName string) bool { + switch cmdName { + case "install", "add", "remove", "update", "sync": + return true + } + return false +} + +// poetryPipPackage is the shape of one entry in `pip list --format=json`. +type poetryPipPackage struct { + Name string `json:"name"` + Version string `json:"version"` +} + +// poetryInstalledPackages runs `poetry run pip list --format=json` inside +// workingDir and returns the installed set as normalised name → version. +// Normalisation matches PEP 503 (lowercase, runs of [-_.] collapsed to "-"), +// the same normalisation applied to lockfile names by PoetryFlexPack. +// Returns nil on any error so the caller falls back to lock-file-driven +// resolution (no regression). +func poetryInstalledPackages(workingDir string) map[string]string { + cmd := exec.Command("poetry", "run", "pip", "list", "--format=json") + cmd.Dir = workingDir + out, err := cmd.Output() + if err != nil { + log.Debug("Poetry build-info: 'poetry run pip list' failed, falling back to lock-file resolution: " + err.Error()) + return nil + } + var list []poetryPipPackage + if err := json.Unmarshal(out, &list); err != nil { + log.Debug("Poetry build-info: failed to parse 'pip list' output: " + err.Error()) + return nil + } + installed := make(map[string]string, len(list)) + for _, p := range list { + installed[normalizePoetryPipName(p.Name)] = p.Version + } + return installed +} + +func normalizePoetryPipName(name string) string { + lower := strings.ToLower(name) + var b strings.Builder + b.Grow(len(lower)) + prevSep := false + for _, r := range lower { + if r == '-' || r == '_' || r == '.' { + if !prevSep && b.Len() > 0 { + b.WriteByte('-') + } + prevSep = true + continue + } + b.WriteRune(r) + prevSep = false + } + return strings.TrimSuffix(b.String(), "-") +} From bf0947157bc400461211fd68db4e3e464fa4ef6d Mon Sep 17 00:00:00 2001 From: Reshmi Date: Tue, 9 Jun 2026 14:52:29 +0530 Subject: [PATCH 2/4] update tests and use show poetry cmd --- poetry_test.go | 104 +++++++++++++++++++++++++++++- utils/buildinfo/buildinfo.go | 99 ++++++++++++++++++++-------- utils/buildinfo/buildinfo_test.go | 90 ++++++++++++++++++++++++++ 3 files changed, 263 insertions(+), 30 deletions(-) create mode 100644 utils/buildinfo/buildinfo_test.go diff --git a/poetry_test.go b/poetry_test.go index 82c8bc204..9728e714c 100644 --- a/poetry_test.go +++ b/poetry_test.go @@ -169,6 +169,106 @@ func testPoetryInstall(t *testing.T, isLegacy bool, useFlexPack bool) { } } +func TestPoetryInstallGroupFilters(t *testing.T) { + initPoetryTest(t) + + // FlexPack routing must be deterministic regardless of ambient env. + restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "JFROG_RUN_NATIVE", "true") + defer restoreEnv() + + // FlexPack shells out to `jf config show` (needs a `jf` on PATH) and to + // `poetry` (needs auth against the Artifactory PyPI virtual repo). + restorePath := buildJfBinaryAndAddToPath(t) + defer restorePath() + restoreAuth := setPoetryHTTPBasicAuth(t) + defer restoreAuth() + + // Populate cli config with 'default' server. + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + cases := []struct { + name string + installArgs []string + wantDeps []string // package names that MUST be present in build-info + unwantedDeps []string // package names that MUST be absent from build-info + }{ + { + name: "only-main-excludes-dev", + installArgs: []string{"--only", "main"}, + wantDeps: []string{"requests"}, + unwantedDeps: []string{"pytest", "black"}, + }, + { + name: "without-dev-excludes-dev", + installArgs: []string{"--without", "dev"}, + wantDeps: []string{"requests"}, + unwantedDeps: []string{"pytest", "black"}, + }, + { + name: "with-dev-includes-dev", + installArgs: []string{"--with", "dev"}, + wantDeps: []string{"requests", "pytest", "black"}, + unwantedDeps: nil, + }, + } + + for i, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + buildNumber := strconv.Itoa(i + 1) + projectPath := createPoetryProject(t, "group-filters-"+tc.name, "poetryproject") + + wd, err := os.Getwd() + require.NoError(t, err) + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) + defer chdirCallback() + + // Isolate the Poetry cache per case to avoid cross-test contamination. + tmpDir, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + unsetCache := clientTestUtils.SetEnvWithCallbackAndAssert(t, "POETRY_CACHE_DIR", filepath.Join(tmpDir, "cache")) + defer unsetCache() + + args := append([]string{"poetry", "install"}, tc.installArgs...) + args = append(args, "--build-name="+tests.PoetryBuildName, "--build-number="+buildNumber) + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + require.NoError(t, jfrogCli.Exec(args...)) + + // Publish and fetch the build info. + require.NoError(t, artifactoryCli.Exec("bp", tests.PoetryBuildName, buildNumber)) + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.PoetryBuildName, buildNumber) + require.NoError(t, err) + require.True(t, found, "build info should be found") + require.Len(t, publishedBuildInfo.BuildInfo.Modules, 1) + + // Collect the set of dependency package names (Id is "name:version"). + depNames := map[string]bool{} + for _, dep := range publishedBuildInfo.BuildInfo.Modules[0].Dependencies { + name := dep.Id + if idx := strings.Index(name, ":"); idx > 0 { + name = name[:idx] + } + depNames[strings.ToLower(name)] = true + } + + for _, want := range tc.wantDeps { + assert.Truef(t, depNames[want], "expected dependency %q in build-info for `poetry install %v`, got %v", + want, tc.installArgs, depNames) + } + for _, unwanted := range tc.unwantedDeps { + assert.Falsef(t, depNames[unwanted], "dependency %q must be EXCLUDED from build-info for `poetry install %v`, got %v", + unwanted, tc.installArgs, depNames) + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.PoetryBuildName, artHttpDetails) + }) + } +} + func testPoetryCmd(t *testing.T, projectPath, buildNumber, module string, expectedDependencies int, expectedType buildinfo.ModuleType, args []string) { wd, err := os.Getwd() assert.NoError(t, err, "Failed to get current directory") @@ -711,8 +811,8 @@ func setPoetryHTTPBasicAuth(t *testing.T) func() { // `go test` process even when several FlexPack-mode tests invoke // buildJfBinaryAndAddToPath sequentially. var ( - jfBinaryOnce sync.Once - jfBinaryDir string + jfBinaryOnce sync.Once + jfBinaryDir string jfBinaryBuildErr error ) diff --git a/utils/buildinfo/buildinfo.go b/utils/buildinfo/buildinfo.go index 22e5937b9..357a9aad6 100644 --- a/utils/buildinfo/buildinfo.go +++ b/utils/buildinfo/buildinfo.go @@ -67,11 +67,8 @@ func GetBuildInfoForPackageManager(pkgManager, workingDir string, buildConfigura // GetPoetryBuildInfo collects build info for Poetry projects. // // cmdName and args describe the poetry sub-command that just ran (e.g. "install", -// "--only", "main"). They are used to decide whether to query the active poetry -// venv for the ground-truth installed set — which is how we honor poetry's -// --only/--without/--with flags in build-info, mirroring the proven UV pattern. -// When cmdName is empty (legacy entry points), no ground-truth query is made -// and the existing lock-file-driven behaviour is preserved. +// "--only", "main"). They are used to decide whether to query poetry for the +// group-filtered installed set func GetPoetryBuildInfo(workingDir string, buildConfiguration *buildUtils.BuildConfiguration, deployerRepo, cmdName string, args []string) error { log.Debug("Collecting Poetry build info from directory: " + workingDir) log.Debug("Deployer repository: " + deployerRepo) @@ -113,13 +110,12 @@ func GetPoetryBuildInfo(workingDir string, buildConfiguration *buildUtils.BuildC log.Info(fmt.Sprintf("Using repository for artifacts: %s", artifactRepo)) // For venv-modifying commands (install/add/remove/update/sync), capture the - // real installed set from poetry so build-info reflects what was actually - // installed — honouring --only/--without/--with without any flag parsing. - // For other commands and on any error this stays nil → legacy behaviour. + // group-filtered set from `poetry show`, forwarding the same + // --only/--with/--without flags that were passed to the poetry command so + // build-info reflects exactly the groups that were installed. var installed map[string]string - _ = args // reserved for future per-arg behaviour; ground truth comes from poetry itself if poetryModifiesVenv(cmdName) { - installed = poetryInstalledPackages(workingDir) + installed = poetryInstalledPackages(workingDir, args) } err = collectPoetryBuildInfo(workingDir, buildName, buildNumber, serverDetails, repoConfig.TargetRepo(), artifactRepo, installed, buildConfiguration) @@ -381,9 +377,10 @@ func CreateAqlQueryForSearch(repo, file string) string { // collectPoetryBuildInfo collects Poetry dependencies and artifacts for build info. // -// installedPackages, when non-nil, is the ground-truth set captured from -// `poetry run pip list`. Only lockfile entries present in this map are included -// in build-info, which is how --only/--without/--with are honoured. +// installedPackages, when non-nil, is the group-filtered set captured from +// `poetry show` (with the install command's --only/--with/--without flags +// forwarded). Only lockfile entries present in this map are included in +// build-info, which is how --only/--without/--with are honoured. func collectPoetryBuildInfo(workingDir, buildName, buildNumber string, serverDetails *config.ServerDetails, _ string, artifactRepo string, installedPackages map[string]string, buildConfiguration *buildUtils.BuildConfiguration) error { log.Debug("Initializing Poetry dependency collection...") @@ -827,8 +824,8 @@ func extractRepoNameFromPypiURL(urlStr string) string { // poetryModifiesVenv reports whether the named poetry sub-command actually // installs/uninstalls packages into the venv. Only for these commands does -// querying the venv for the installed set make sense — for lock/build/publish -// etc. the venv state is unrelated to the operation. +// querying poetry for the group-filtered installed set make sense — for +// lock/build/publish etc. the installed state is unrelated to the operation. func poetryModifiesVenv(cmdName string) bool { switch cmdName { case "install", "add", "remove", "update", "sync": @@ -837,29 +834,75 @@ func poetryModifiesVenv(cmdName string) bool { return false } -// poetryPipPackage is the shape of one entry in `pip list --format=json`. -type poetryPipPackage struct { +// poetryShowPackage is the shape of one entry in `poetry show --format json`. +// poetry show emits more fields (installed_status, description, ...) but only +// name and version are needed here. +type poetryShowPackage struct { Name string `json:"name"` Version string `json:"version"` } -// poetryInstalledPackages runs `poetry run pip list --format=json` inside -// workingDir and returns the installed set as normalised name → version. -// Normalisation matches PEP 503 (lowercase, runs of [-_.] collapsed to "-"), -// the same normalisation applied to lockfile names by PoetryFlexPack. -// Returns nil on any error so the caller falls back to lock-file-driven -// resolution (no regression). -func poetryInstalledPackages(workingDir string) map[string]string { - cmd := exec.Command("poetry", "run", "pip", "list", "--format=json") +// poetryShowGroupArgs extracts the dependency-group filter flags +// (--only/--with/--without and their values) from the poetry command args so +// they can be forwarded verbatim to `poetry show`. Other install flags are not +// valid for `poetry show` and are intentionally dropped. +// +// includeAll is true when the args request every group (e.g. --all-groups), in +// which case no `poetry show` filtering is needed — the legacy include-all +// behaviour already produces the correct result. +func poetryShowGroupArgs(args []string) (groupArgs []string, includeAll bool) { + groupFlags := map[string]bool{"--only": true, "--with": true, "--without": true} + for i := 0; i < len(args); i++ { + a := args[i] + if a == "--all-groups" { + return nil, true + } + // --flag=value form + if eq := strings.IndexByte(a, '='); eq > 0 { + if groupFlags[a[:eq]] { + groupArgs = append(groupArgs, a) + } + continue + } + // --flag value form + if groupFlags[a] { + groupArgs = append(groupArgs, a) + if i+1 < len(args) { + groupArgs = append(groupArgs, args[i+1]) + i++ + } + } + } + return groupArgs, false +} + +// poetryInstalledPackages runs `poetry show --format json` inside workingDir, +// forwarding the dependency-group filter flags (--only/--with/--without) that +// were passed to the poetry command. poetry show resolves the activated groups +// via its own solver, so the result is exactly the set produced by the +// group-filtered install — honouring --only/--without/--with. +// +// Returns the set as normalised name → version. Normalisation matches PEP 503 +// (lowercase, runs of [-_.] collapsed to "-"), the same normalisation applied +// to lockfile names by PoetryFlexPack. Returns nil on any error so the caller +// falls back to lock-file-driven resolution (no regression). +func poetryInstalledPackages(workingDir string, args []string) map[string]string { + groupArgs, includeAll := poetryShowGroupArgs(args) + if includeAll { + // Every group requested → legacy include-all behaviour is already correct. + return nil + } + cmdArgs := append([]string{"show", "--format", "json"}, groupArgs...) + cmd := exec.Command("poetry", cmdArgs...) cmd.Dir = workingDir out, err := cmd.Output() if err != nil { - log.Debug("Poetry build-info: 'poetry run pip list' failed, falling back to lock-file resolution: " + err.Error()) + log.Debug("Poetry build-info: 'poetry show' failed, falling back to lock-file resolution: " + err.Error()) return nil } - var list []poetryPipPackage + var list []poetryShowPackage if err := json.Unmarshal(out, &list); err != nil { - log.Debug("Poetry build-info: failed to parse 'pip list' output: " + err.Error()) + log.Debug("Poetry build-info: failed to parse 'poetry show' output: " + err.Error()) return nil } installed := make(map[string]string, len(list)) diff --git a/utils/buildinfo/buildinfo_test.go b/utils/buildinfo/buildinfo_test.go new file mode 100644 index 000000000..aab14cb54 --- /dev/null +++ b/utils/buildinfo/buildinfo_test.go @@ -0,0 +1,90 @@ +package buildinfo + +import ( + "reflect" + "testing" +) + +// TestPoetryShowGroupArgs verifies that only the dependency-group filter flags +// (--only/--with/--without) are forwarded to `poetry show`, that both the +// "--flag value" and "--flag=value" forms are handled, and that --all-groups +// short-circuits to include-all (legacy behaviour). +func TestPoetryShowGroupArgs(t *testing.T) { + tests := []struct { + name string + args []string + wantGroupArgs []string + wantIncludeAll bool + }{ + { + name: "only main, space form", + args: []string{"--only", "main", "--no-root"}, + wantGroupArgs: []string{"--only", "main"}, + }, + { + name: "only main, equals form", + args: []string{"--only=main", "--no-root"}, + wantGroupArgs: []string{"--only=main"}, + }, + { + name: "without dev", + args: []string{"--without", "dev"}, + wantGroupArgs: []string{"--without", "dev"}, + }, + { + name: "with optional, multiple group flags", + args: []string{"--with", "docs", "--without", "dev"}, + wantGroupArgs: []string{"--with", "docs", "--without", "dev"}, + }, + { + name: "all-groups short-circuits to include-all", + args: []string{"--all-groups"}, + wantGroupArgs: nil, + wantIncludeAll: true, + }, + { + name: "no group flags drops everything else", + args: []string{"--no-root", "--compile", "-E", "extra"}, + wantGroupArgs: nil, + }, + { + name: "trailing group flag without value is forwarded as-is", + args: []string{"--only"}, + wantGroupArgs: []string{"--only"}, + }, + { + name: "empty args", + args: nil, + wantGroupArgs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotGroupArgs, gotIncludeAll := poetryShowGroupArgs(tt.args) + if gotIncludeAll != tt.wantIncludeAll { + t.Errorf("includeAll = %v, want %v", gotIncludeAll, tt.wantIncludeAll) + } + if !reflect.DeepEqual(gotGroupArgs, tt.wantGroupArgs) { + t.Errorf("groupArgs = %#v, want %#v", gotGroupArgs, tt.wantGroupArgs) + } + }) + } +} + +// TestNormalizePoetryPipName verifies PEP 503 normalisation: lowercase with runs +// of [-_.] collapsed to a single "-" and no leading/trailing separator. +func TestNormalizePoetryPipName(t *testing.T) { + tests := map[string]string{ + "Requests": "requests", + "ruamel.yaml": "ruamel-yaml", + "my__weird..name": "my-weird-name", + "Already-Normalised": "already-normalised", + "typing_extensions": "typing-extensions", + } + for in, want := range tests { + if got := normalizePoetryPipName(in); got != want { + t.Errorf("normalizePoetryPipName(%q) = %q, want %q", in, got, want) + } + } +} From f3fcb00a5d1990def3687b0b04d3227b9be8654a Mon Sep 17 00:00:00 2001 From: Reshmi Date: Tue, 9 Jun 2026 23:06:23 +0530 Subject: [PATCH 3/4] update bulid info go deps --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 0cb8b2e15..b3c55a0ed 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/buger/jsonparser v1.2.0 github.com/gocarina/gocsv v0.0.0-20260523204920-c264028e67ea github.com/jfrog/archiver/v3 v3.6.3 - github.com/jfrog/build-info-go v1.13.1-0.20260528065004-80409c046540 + github.com/jfrog/build-info-go v1.13.1-0.20260609173330-a8a3ed3919af github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260511133105-55a0ab56fd64 github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260601110159-16e27949b870 @@ -247,7 +247,7 @@ require ( // replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260416054314-f941180fccc3 -replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.13.1-0.20260603044611-897a097a7031 +// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.13.1-0.20260603044611-897a097a7031 // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260518123155-036d9195c4e9 diff --git a/go.sum b/go.sum index 9c80499e1..1ca0e0ee7 100644 --- a/go.sum +++ b/go.sum @@ -400,8 +400,8 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jfrog/archiver/v3 v3.6.3 h1:hkAmPjBw393tPmQ07JknLNWFNZjXdy2xFEnOW9wwOxI= github.com/jfrog/archiver/v3 v3.6.3/go.mod h1:5V9l+Fte30Y4qe9dUOAd3yNTf8lmtVNuhKNrvI8PMhg= -github.com/jfrog/build-info-go v1.13.1-0.20260603044611-897a097a7031 h1:/OkgqPsrHP05VWj/o+nygOj1eMxtvq7So7iEtKIUO/o= -github.com/jfrog/build-info-go v1.13.1-0.20260603044611-897a097a7031/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= +github.com/jfrog/build-info-go v1.13.1-0.20260609173330-a8a3ed3919af h1:ubWSqnwIip7pdr6PybXiynb3rUVwdNBYEGnb23JA0L4= +github.com/jfrog/build-info-go v1.13.1-0.20260609173330-a8a3ed3919af/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= github.com/jfrog/froggit-go v1.22.0 h1:eeN5F8sOUo+h2cXkzArAu4nvSdjkDTAZtgqwrct70qg= github.com/jfrog/froggit-go v1.22.0/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8= From 0e91b463cafdb2aa75e21dd218e0d7d8ec470fde Mon Sep 17 00:00:00 2001 From: reshmifrog Date: Tue, 9 Jun 2026 23:32:15 +0530 Subject: [PATCH 4/4] Remove TestPoetryInstallGroupFilters function Removed TestPoetryInstallGroupFilters function --- poetry_test.go | 100 ------------------------------------------------- 1 file changed, 100 deletions(-) diff --git a/poetry_test.go b/poetry_test.go index 9728e714c..c6632143e 100644 --- a/poetry_test.go +++ b/poetry_test.go @@ -169,106 +169,6 @@ func testPoetryInstall(t *testing.T, isLegacy bool, useFlexPack bool) { } } -func TestPoetryInstallGroupFilters(t *testing.T) { - initPoetryTest(t) - - // FlexPack routing must be deterministic regardless of ambient env. - restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "JFROG_RUN_NATIVE", "true") - defer restoreEnv() - - // FlexPack shells out to `jf config show` (needs a `jf` on PATH) and to - // `poetry` (needs auth against the Artifactory PyPI virtual repo). - restorePath := buildJfBinaryAndAddToPath(t) - defer restorePath() - restoreAuth := setPoetryHTTPBasicAuth(t) - defer restoreAuth() - - // Populate cli config with 'default' server. - oldHomeDir, newHomeDir := prepareHomeDir(t) - defer func() { - clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) - clientTestUtils.RemoveAllAndAssert(t, newHomeDir) - }() - - cases := []struct { - name string - installArgs []string - wantDeps []string // package names that MUST be present in build-info - unwantedDeps []string // package names that MUST be absent from build-info - }{ - { - name: "only-main-excludes-dev", - installArgs: []string{"--only", "main"}, - wantDeps: []string{"requests"}, - unwantedDeps: []string{"pytest", "black"}, - }, - { - name: "without-dev-excludes-dev", - installArgs: []string{"--without", "dev"}, - wantDeps: []string{"requests"}, - unwantedDeps: []string{"pytest", "black"}, - }, - { - name: "with-dev-includes-dev", - installArgs: []string{"--with", "dev"}, - wantDeps: []string{"requests", "pytest", "black"}, - unwantedDeps: nil, - }, - } - - for i, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - buildNumber := strconv.Itoa(i + 1) - projectPath := createPoetryProject(t, "group-filters-"+tc.name, "poetryproject") - - wd, err := os.Getwd() - require.NoError(t, err) - chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) - defer chdirCallback() - - // Isolate the Poetry cache per case to avoid cross-test contamination. - tmpDir, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - unsetCache := clientTestUtils.SetEnvWithCallbackAndAssert(t, "POETRY_CACHE_DIR", filepath.Join(tmpDir, "cache")) - defer unsetCache() - - args := append([]string{"poetry", "install"}, tc.installArgs...) - args = append(args, "--build-name="+tests.PoetryBuildName, "--build-number="+buildNumber) - - jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") - require.NoError(t, jfrogCli.Exec(args...)) - - // Publish and fetch the build info. - require.NoError(t, artifactoryCli.Exec("bp", tests.PoetryBuildName, buildNumber)) - publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.PoetryBuildName, buildNumber) - require.NoError(t, err) - require.True(t, found, "build info should be found") - require.Len(t, publishedBuildInfo.BuildInfo.Modules, 1) - - // Collect the set of dependency package names (Id is "name:version"). - depNames := map[string]bool{} - for _, dep := range publishedBuildInfo.BuildInfo.Modules[0].Dependencies { - name := dep.Id - if idx := strings.Index(name, ":"); idx > 0 { - name = name[:idx] - } - depNames[strings.ToLower(name)] = true - } - - for _, want := range tc.wantDeps { - assert.Truef(t, depNames[want], "expected dependency %q in build-info for `poetry install %v`, got %v", - want, tc.installArgs, depNames) - } - for _, unwanted := range tc.unwantedDeps { - assert.Falsef(t, depNames[unwanted], "dependency %q must be EXCLUDED from build-info for `poetry install %v`, got %v", - unwanted, tc.installArgs, depNames) - } - - inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.PoetryBuildName, artHttpDetails) - }) - } -} - func testPoetryCmd(t *testing.T, projectPath, buildNumber, module string, expectedDependencies int, expectedType buildinfo.ModuleType, args []string) { wd, err := os.Getwd() assert.NoError(t, err, "Failed to get current directory")