Skip to content

Commit

Permalink
Improved tech detection when tech is provided by user (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
eranturgeman authored Sep 15, 2024
1 parent 1bcec51 commit f6f8065
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 7 deletions.
19 changes: 15 additions & 4 deletions utils/techutils/techutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
)
const Pypi = "pypi"

var AllTechnologiesStrings = []string{Maven.String(), Gradle.String(), Npm.String(), Pnpm.String(), Yarn.String(), Go.String(), Pip.String(), Pipenv.String(), Poetry.String(), Nuget.String(), Dotnet.String(), Docker.String(), Oci.String(), Conan.String()}

type CodeLanguage string

const (
Expand Down Expand Up @@ -400,13 +402,19 @@ func isExclude(path string, techData TechData) bool {
func mapWorkingDirectoriesToTechnologies(workingDirectoryToIndicators map[string][]string, excludedTechAtWorkingDir map[string][]Technology, requestedTechs []Technology, requestedDescriptors map[Technology][]string) (technologiesDetected map[Technology]map[string][]string, err error) {
// Get the relevant technologies to check
technologies := requestedTechs
var techProvidedByUser bool
if len(technologies) == 0 {
technologies = GetAllTechnologiesList()
} else {
// If the project's technology was provided by the user, and isn't detected by us, we want to enable capturing the technology by its descriptor as well as by its indicators.
// In case we execute our auto-detection we want to avoid that since it may lead to collisions between package managers with the same descriptor (like Npm and Yarn)
techProvidedByUser = true
log.Debug(fmt.Sprintf("Technologies were identified either from the command flags supplied by the user or inferred from the provided installation command. Detected technologies: %s.", technologies))
}
technologiesDetected = make(map[Technology]map[string][]string)
// Map working directories to technologies
for _, tech := range technologies {
if techWorkingDirs, e := getTechInformationFromWorkingDir(tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, requestedDescriptors); e != nil {
if techWorkingDirs, e := getTechInformationFromWorkingDir(tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, requestedDescriptors, techProvidedByUser); e != nil {
err = errors.Join(err, fmt.Errorf("failed to get information from working directory for %s", tech))
} else if len(techWorkingDirs) > 0 {
// Found indicators of the technology, add to detected.
Expand All @@ -423,11 +431,12 @@ func mapWorkingDirectoriesToTechnologies(workingDirectoryToIndicators map[string
return
}

func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicators map[string][]string, excludedTechAtWorkingDir map[string][]Technology, requestedDescriptors map[Technology][]string) (techWorkingDirs map[string][]string, err error) {
func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicators map[string][]string, excludedTechAtWorkingDir map[string][]Technology, requestedDescriptors map[Technology][]string, techProvidedByUser bool) (techWorkingDirs map[string][]string, err error) {
techWorkingDirs = make(map[string][]string)
for wd, indicators := range workingDirectoryToIndicators {
descriptorsAtWd := []string{}
foundIndicator := false
foundDescriptor := false
if isTechExcludedInWorkingDir(tech, wd, excludedTechAtWorkingDir) {
// Exclude this technology from this working directory
continue
Expand All @@ -436,6 +445,7 @@ func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicat
for _, path := range indicators {
if tech.isDescriptor(path) || isRequestedDescriptor(path, requestedDescriptors[tech]) {
descriptorsAtWd = append(descriptorsAtWd, path)
foundDescriptor = true
}
if indicator, e := tech.isIndicator(path); e != nil {
err = errors.Join(err, fmt.Errorf("failed to check if %s is an indicator of %s: %w", path, tech, e))
Expand All @@ -444,8 +454,9 @@ func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicat
foundIndicator = true
}
}
if foundIndicator {
// Found indicators of the technology in the current working directory, add to detected.
if foundIndicator || (foundDescriptor && techProvidedByUser) {
// If indicators of the technology were found in the current working directory, add to detected.
// If descriptors were found for a specific tech that was provided by the user, we add the descriptor to detected.
techWorkingDirs[wd] = descriptorsAtWd
}
}
Expand Down
45 changes: 42 additions & 3 deletions utils/techutils/techutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@ func TestMapWorkingDirectoriesToTechnologies(t *testing.T) {
excludedTechAtWorkingDir map[string][]Technology
requestedTechs []Technology
requestedDescriptors map[Technology][]string

expected map[Technology]map[string][]string
expected map[Technology]map[string][]string
}{
{
name: "noTechTest",
Expand Down Expand Up @@ -210,6 +209,20 @@ func TestMapWorkingDirectoriesToTechnologies(t *testing.T) {
Dotnet: {"dir": {filepath.Join("dir", "project.sln"), filepath.Join("dir", "sub1", "project.csproj")}},
},
},
{
// When tech is requested by user we detect technology by indicator as well as by descriptors, therefore we can relate descriptor files to tech even when indicator doesn't exist
name: "tech requested by user test",
workingDirectoryToIndicators: map[string][]string{
"dir3": {filepath.Join("dir3", "package.json")},
projectDir: {filepath.Join(projectDir, "pyproject.toml")},
},
requestedTechs: []Technology{Yarn, Poetry},
requestedDescriptors: noRequestSpecialDescriptors,
expected: map[Technology]map[string][]string{
Yarn: {"dir3": {filepath.Join("dir3", "package.json")}},
Poetry: {projectDir: {filepath.Join(projectDir, "pyproject.toml")}},
},
},
}

for _, test := range tests {
Expand Down Expand Up @@ -385,6 +398,7 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
"dir": {filepath.Join("dir", "package.json"), filepath.Join("dir", "package-lock.json"), filepath.Join("dir", "build.gradle.kts"), filepath.Join("dir", "project.sln"), filepath.Join("dir", "blabla.txt")},
"directory": {filepath.Join("directory", "npm-shrinkwrap.json")},
"dir3": {filepath.Join("dir3", "package.json"), filepath.Join("dir3", ".yarn")},
"dir4": {filepath.Join("dir4", "package.json")},
projectDir: {filepath.Join(projectDir, "pyproject.toml")},
filepath.Join("dir3", "dir"): {filepath.Join("dir3", "dir", "package.json"), filepath.Join("dir3", "dir", "pnpm-lock.yaml")},
filepath.Join("dir", "dir2"): {filepath.Join("dir", "dir2", "go.mod")},
Expand All @@ -403,12 +417,14 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
name string
tech Technology
requestedDescriptors map[Technology][]string
techProvidedByUser bool
expected map[string][]string
}{
{
name: "mavenTest",
tech: Maven,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{
"folder": {
filepath.Join("folder", "pom.xml"),
Expand All @@ -421,33 +437,39 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
name: "npmTest",
tech: Npm,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{
"dir": {filepath.Join("dir", "package.json")},
"dir4": {filepath.Join("dir4", "package.json")},
"directory": {},
},
},
{
name: "pnpmTest",
tech: Pnpm,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{filepath.Join("dir3", "dir"): {filepath.Join("dir3", "dir", "package.json")}},
},
{
name: "yarnTest",
tech: Yarn,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{"dir3": {filepath.Join("dir3", "package.json")}},
},
{
name: "golangTest",
tech: Go,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{filepath.Join("dir", "dir2"): {filepath.Join("dir", "dir2", "go.mod")}},
},
{
name: "pipTest",
tech: Pip,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{
filepath.Join("users_dir", "test", "package"): {filepath.Join("users_dir", "test", "package", "setup.py")},
filepath.Join("users_dir", "test", "package2"): {filepath.Join("users_dir", "test", "package2", "requirements.txt")},
Expand All @@ -458,6 +480,7 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
name: "pipRequestedDescriptorTest",
tech: Pip,
requestedDescriptors: map[Technology][]string{Pip: {"blabla.txt"}},
techProvidedByUser: false,
expected: map[string][]string{
"dir": {filepath.Join("dir", "blabla.txt")},
filepath.Join("users_dir", "test", "package"): {filepath.Join("users_dir", "test", "package", "setup.py")},
Expand All @@ -469,12 +492,14 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
name: "pipenvTest",
tech: Pipenv,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{filepath.Join("users", "test", "package"): {filepath.Join("users", "test", "package", "Pipfile")}},
},
{
name: "gradleTest",
tech: Gradle,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{
filepath.Join("users", "test", "package"): {filepath.Join("users", "test", "package", "build.gradle")},
"dir": {filepath.Join("dir", "build.gradle.kts")},
Expand All @@ -484,19 +509,33 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
name: "nugetTest",
tech: Nuget,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{"dir": {filepath.Join("dir", "project.sln"), filepath.Join("dir", "sub1", "project.csproj")}},
},
{
name: "dotnetTest",
tech: Dotnet,
requestedDescriptors: map[Technology][]string{},
techProvidedByUser: false,
expected: map[string][]string{"dir": {filepath.Join("dir", "project.sln"), filepath.Join("dir", "sub1", "project.csproj")}},
},
// When tech is provided by the user we detect technology by indicator and descriptors and not just by indicator. Test cases are provided only for technologies that might experience conflicts.
{
name: "yarnTestWithProvidedTechFromUser",
tech: Yarn,
requestedDescriptors: make(map[Technology][]string),
techProvidedByUser: true,
expected: map[string][]string{
"dir": {filepath.Join("dir", "package.json")},
"dir3": {filepath.Join("dir3", "package.json")},
"dir4": {filepath.Join("dir4", "package.json")},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
techInformation, err := getTechInformationFromWorkingDir(test.tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, test.requestedDescriptors)
techInformation, err := getTechInformationFromWorkingDir(test.tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, test.requestedDescriptors, test.techProvidedByUser)
assert.NoError(t, err)
expectedKeys := maps.Keys(test.expected)
actualKeys := maps.Keys(techInformation)
Expand Down

0 comments on commit f6f8065

Please sign in to comment.