feat: Add R cataloger (#1790)

Add a cataloger that detects installed R packages by looking for DESCRIPTION
files. The base R package is now picked up in coverageImage tests in
test/cli/packages_cmd_test.go, so increment expected package counts for the
tests that use that image.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
This commit is contained in:
William Murphy 2023-05-10 12:30:11 -04:00 committed by GitHub
parent 0580328ad9
commit da3624644a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 2476 additions and 5 deletions

View file

@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "7.1.5"
JSONSchemaVersion = "7.1.6"
)

View file

@ -29,6 +29,9 @@ can be extended to include specific package metadata struct shapes in the future
// not matter as long as it is exported.
// TODO: this should be generated from reflection of whats in the pkg package
// Should be created during generation below; use reflection's ability to
// create types at runtime.
// should be same name as struct minus metadata
type artifactMetadataContainer struct {
Alpm pkg.AlpmMetadata
Apk pkg.ApkMetadata
@ -56,6 +59,7 @@ type artifactMetadataContainer struct {
PythonPackage pkg.PythonPackageMetadata
PythonPipfilelock pkg.PythonPipfileLockMetadata
PythonRequirements pkg.PythonRequirementsMetadata
RDescriptionFile pkg.RDescriptionFileMetadata
Rebar pkg.RebarLockMetadata
Rpm pkg.RpmMetadata
RustCargo pkg.CargoPackageMetadata

File diff suppressed because it is too large Load diff

View file

@ -52,6 +52,8 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from linux kernel module files"
case pkg.NixPkg:
answer = "acquired package info from nix store path"
case pkg.Rpkg:
answer = "acquired package info from R-package DESCRIPTION file"
default:
answer = "acquired package info from the following paths"
}

View file

@ -223,6 +223,14 @@ func Test_SourceInfo(t *testing.T) {
"from nix store path",
},
},
{
input: pkg.Package{
Type: pkg.Rpkg,
},
expected: []string{
"acquired package info from R-package DESCRIPTION file",
},
},
}
var pkgTypes []pkg.Type
for _, test := range tests {

View file

@ -28,6 +28,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/php"
"github.com/anchore/syft/syft/pkg/cataloger/portage"
"github.com/anchore/syft/syft/pkg/cataloger/python"
"github.com/anchore/syft/syft/pkg/cataloger/r"
"github.com/anchore/syft/syft/pkg/cataloger/rpm"
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
"github.com/anchore/syft/syft/pkg/cataloger/rust"
@ -53,6 +54,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
php.NewComposerInstalledCataloger(),
portage.NewPortageCataloger(),
python.NewPythonPackageCataloger(),
r.NewPackageCataloger(),
rpm.NewRpmDBCataloger(),
ruby.NewGemSpecCataloger(),
sbom.NewSBOMCataloger(),
@ -121,6 +123,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
portage.NewPortageCataloger(),
python.NewPythonIndexCataloger(),
python.NewPythonPackageCataloger(),
r.NewPackageCataloger(),
rpm.NewFileCataloger(),
rpm.NewRpmDBCataloger(),
ruby.NewGemFileLockCataloger(),

View file

@ -0,0 +1,13 @@
package r
import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const catalogerName = "r-package-cataloger"
// NewPackageCataloger returns a new R cataloger object based on detection of R package DESCRIPTION files.
func NewPackageCataloger() *generic.Cataloger {
return generic.NewCataloger(catalogerName).
WithParserByGlobs(parseDescriptionFile, "**/DESCRIPTION")
}

View file

@ -0,0 +1,60 @@
package r
import (
"testing"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
"github.com/anchore/syft/syft/source"
)
func TestRPackageCataloger(t *testing.T) {
expectedPkgs := []pkg.Package{
{
Name: "base",
Version: "4.3.0",
FoundBy: "r-package-cataloger",
Locations: source.NewLocationSet(source.NewLocation("base/DESCRIPTION")),
Licenses: []string{"Part of R 4.3.0"},
Language: pkg.R,
Type: pkg.Rpkg,
PURL: "pkg:cran/base@4.3.0",
MetadataType: pkg.RDescriptionFileMetadataType,
Metadata: pkg.RDescriptionFileMetadata{
Title: "The R Base Package",
Description: "Base R functions.",
Author: "R Core Team and contributors worldwide",
Maintainer: "R Core Team <do-use-Contact-address@r-project.org>",
Built: "R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix",
Suggests: []string{"methods"},
},
},
{
Name: "stringr",
Version: "1.5.0.9000",
FoundBy: "r-package-cataloger",
Locations: source.NewLocationSet(source.NewLocation("stringr/DESCRIPTION")),
Licenses: []string{"MIT + file LICENSE"},
Language: pkg.R,
Type: pkg.Rpkg,
PURL: "pkg:cran/stringr@1.5.0.9000",
MetadataType: pkg.RDescriptionFileMetadataType,
Metadata: pkg.RDescriptionFileMetadata{
Title: "Simple, Consistent Wrappers for Common String Operations",
Description: "A consistent, simple and easy to use set of wrappers around the fantastic 'stringi' package. All function and argument names (and positions) are consistent, all functions deal with \"NA\"'s and zero length vectors in the same way, and the output from one function is easy to feed into the input of another.",
URL: []string{"https://stringr.tidyverse.org", "https://github.com/tidyverse/stringr"},
Imports: []string{
"cli", "glue (>= 1.6.1)", "lifecycle (>= 1.0.3)", "magrittr",
"rlang (>= 1.0.0)", "stringi (>= 1.5.3)", "vctrs (>= 0.4.0)",
},
Depends: []string{"R (>= 3.3)"},
Suggests: []string{"covr", "dplyr", "gt", "htmltools", "htmlwidgets", "knitr", "rmarkdown", "testthat (>= 3.0.0)", "tibble"},
},
},
}
// TODO: relationships are not under test yet
var expectedRelationships []artifact.Relationship
pkgtest.NewCatalogTester().FromDirectory(t, "test-fixtures/installed").Expects(expectedPkgs, expectedRelationships).TestCataloger(t, NewPackageCataloger())
}

View file

@ -0,0 +1,32 @@
package r
import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func newPackage(pd parseData, locations ...source.Location) pkg.Package {
locationSet := source.NewLocationSet()
for _, loc := range locations {
locationSet.Add(loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
}
result := pkg.Package{
Name: pd.Package,
Version: pd.Version,
Locations: locationSet,
Licenses: []string{pd.License},
Language: pkg.R,
Type: pkg.Rpkg,
PURL: packageURL(pd),
MetadataType: pkg.RDescriptionFileMetadataType,
Metadata: pd.RDescriptionFileMetadata,
}
result.SetID()
return result
}
func packageURL(m parseData) string {
return packageurl.NewPackageURL("cran", "", m.Package, m.Version, nil, "").ToString()
}

View file

@ -0,0 +1,14 @@
package r
import "testing"
func Test_newPackage(t *testing.T) {
testCases := []struct {
name string
}{}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
})
}
}

View file

@ -0,0 +1,147 @@
package r
import (
"bufio"
"io"
"regexp"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
)
/* some examples of license strings found in DESCRIPTION files:
find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'License:' | sort | uniq
License: GPL
License: GPL (>= 2)
License: GPL (>=2)
License: GPL(>=2)
License: GPL (>= 2) | file LICENCE
License: GPL-2 | GPL-3
License: GPL-3
License: LGPL (>= 2)
License: LGPL (>= 2.1)
License: MIT + file LICENSE
License: Part of R 4.3.0
License: Unlimited
*/
func parseDescriptionFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
values := extractFieldsFromDescriptionFile(reader)
m := parseDataFromDescriptionMap(values)
p := newPackage(m, []source.Location{reader.Location}...)
if p.Name == "" || p.Version == "" {
return nil, nil, nil
}
return []pkg.Package{p}, nil, nil
}
type parseData struct {
Package string
Version string
License string
pkg.RDescriptionFileMetadata
}
func parseDataFromDescriptionMap(values map[string]string) parseData {
return parseData{
License: values["License"],
Package: values["Package"],
Version: values["Version"],
RDescriptionFileMetadata: pkg.RDescriptionFileMetadata{
Title: values["Title"],
Description: cleanMultiLineValue(values["Description"]),
Maintainer: values["Maintainer"],
URL: commaSeparatedList(values["URL"]),
Depends: commaSeparatedList(values["Depends"]),
Imports: commaSeparatedList(values["Imports"]),
Suggests: commaSeparatedList(values["Suggests"]),
NeedsCompilation: yesNoToBool(values["NeedsCompilation"]),
Author: values["Author"],
Repository: values["Repository"],
Built: values["Built"],
},
}
}
func yesNoToBool(s string) bool {
/*
$ docker run --rm -it rocker/r-ver bash
$ install2.r ggplot2 dplyr mlr3 caret # just some packages for a larger sample
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'NeedsCompilation:' | sort | uniq
NeedsCompilation: no
NeedsCompilation: yes
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'NeedsCompilation:' | wc -l
105
*/
return strings.EqualFold(s, "yes")
}
func commaSeparatedList(s string) []string {
var result []string
split := strings.Split(s, ",")
for _, piece := range split {
value := strings.TrimSpace(piece)
if value == "" {
continue
}
result = append(result, value)
}
return result
}
var space = regexp.MustCompile(`\s+`)
func cleanMultiLineValue(s string) string {
return space.ReplaceAllString(s, " ")
}
func extractFieldsFromDescriptionFile(reader io.Reader) map[string]string {
result := make(map[string]string)
key := ""
var valueFragment strings.Builder
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
// line is like Key: Value -> start capturing value; close out previous value
// line is like \t\t continued value -> append to existing value
if len(line) == 0 {
continue
}
if startsWithWhitespace(line) {
// we're continuing a value
if key == "" {
continue
}
valueFragment.WriteByte('\n')
valueFragment.WriteString(strings.TrimSpace(line))
} else {
if key != "" {
// capture previous value
result[key] = valueFragment.String()
key = ""
valueFragment = strings.Builder{}
}
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}
key = parts[0]
valueFragment.WriteString(strings.TrimSpace(parts[1]))
}
}
if key != "" {
result[key] = valueFragment.String()
}
return result
}
func startsWithWhitespace(s string) bool {
if s == "" {
return false
}
return s[0] == ' ' || s[0] == '\t'
}

View file

@ -0,0 +1,131 @@
package r
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func Test_parseDescriptionFile(t *testing.T) {
type packageAssertions []func(*testing.T, []pkg.Package)
tests := []struct {
name string
assertions packageAssertions
fixture string
}{
{
name: "no package is returned if no version found",
fixture: filepath.Join("test-fixtures", "map-parse", "no-version"),
assertions: packageAssertions{
func(t *testing.T, p []pkg.Package) {
assert.Empty(t, p)
},
},
},
{
name: "no package is returned if no package name found",
fixture: filepath.Join("test-fixtures", "map-parse", "no-name"),
assertions: packageAssertions{
func(t *testing.T, p []pkg.Package) {
assert.Empty(t, p)
},
},
},
{
name: "package return if both name and version found",
fixture: filepath.Join("test-fixtures", "map-parse", "simple"),
assertions: packageAssertions{
func(t *testing.T, p []pkg.Package) {
assert.Equal(t, 1, len(p))
assert.Equal(t, "base", p[0].Name)
assert.Equal(t, "4.3.0", p[0].Version)
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(tt.fixture)
input := source.LocationReadCloser{
Location: source.NewLocation(tt.fixture),
ReadCloser: f,
}
got, _, err := parseDescriptionFile(nil, nil, input)
assert.NoError(t, err)
for _, assertion := range tt.assertions {
assertion(t, got)
}
})
}
}
func Test_extractFieldsFromDescriptionFile(t *testing.T) {
tests := []struct {
name string
fixture string
want map[string]string
}{
{
name: "go case",
fixture: "test-fixtures/map-parse/simple",
want: map[string]string{
"Package": "base",
"Version": "4.3.0",
"Suggests": "methods",
"Built": "R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix",
},
},
{
name: "bad cases",
fixture: "test-fixtures/map-parse/bad",
want: map[string]string{
"Key": "",
"Whitespace": "",
},
},
{
name: "multiline key-value",
fixture: "test-fixtures/map-parse/multiline",
want: map[string]string{
"Description": `A consistent, simple and easy to use set of wrappers around
the fantastic 'stringi' package. All function and argument names (and
positions) are consistent, all functions deal with "NA"'s and zero
length vectors in the same way, and the output from one function is
easy to feed into the input of another.`,
"License": "MIT + file LICENSE",
"Key": "value",
},
},
{
name: "eof multiline",
fixture: "test-fixtures/map-parse/eof-multiline",
want: map[string]string{
"License": "MIT + file LICENSE",
"Description": `A consistent, simple and easy to use set of wrappers around
the fantastic 'stringi' package. All function and argument names (and
positions) are consistent, all functions deal with "NA"'s and zero
length vectors in the same way, and the output from one function is
easy to feed into the input of another.`,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
file, err := os.Open(test.fixture)
require.NoError(t, err)
result := extractFieldsFromDescriptionFile(file)
assert.Equal(t, test.want, result)
})
}
}

View file

@ -0,0 +1,46 @@
Package: stringr
Title: Simple, Consistent Wrappers for Common String Operations
Version: 1.5.0.9000
Authors@R:
c(person(given = "Hadley",
family = "Wickham",
role = c("aut", "cre", "cph"),
email = "hadley@rstudio.com"),
person(given = "RStudio",
role = c("cph", "fnd")))
Description: A consistent, simple and easy to use set of wrappers around
the fantastic 'stringi' package. All function and argument names (and
positions) are consistent, all functions deal with "NA"'s and zero
length vectors in the same way, and the output from one function is
easy to feed into the input of another.
License: MIT + file LICENSE
URL: https://stringr.tidyverse.org, https://github.com/tidyverse/stringr
BugReports: https://github.com/tidyverse/stringr/issues
Depends:
R (>= 3.3)
Imports:
cli,
glue (>= 1.6.1),
lifecycle (>= 1.0.3),
magrittr,
rlang (>= 1.0.0),
stringi (>= 1.5.3),
vctrs (>= 0.4.0)
Suggests:
covr,
dplyr,
gt,
htmltools,
htmlwidgets,
knitr,
rmarkdown,
testthat (>= 3.0.0),
tibble
VignetteBuilder:
knitr
Config/Needs/website: tidyverse/tidytemplate
Config/testthat/edition: 3
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.1

View file

@ -0,0 +1,11 @@
Package: base
Version: 4.3.0
Priority: base
Title: The R Base Package
Author: R Core Team and contributors worldwide
Maintainer: R Core Team <do-use-Contact-address@r-project.org>
Contact: R-help mailing list <r-help@r-project.org>
Description: Base R functions.
License: Part of R 4.3.0
Suggests: methods
Built: R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix

View file

@ -0,0 +1,46 @@
Package: stringr
Title: Simple, Consistent Wrappers for Common String Operations
Version: 1.5.0.9000
Authors@R:
c(person(given = "Hadley",
family = "Wickham",
role = c("aut", "cre", "cph"),
email = "hadley@rstudio.com"),
person(given = "RStudio",
role = c("cph", "fnd")))
Description: A consistent, simple and easy to use set of wrappers around
the fantastic 'stringi' package. All function and argument names (and
positions) are consistent, all functions deal with "NA"'s and zero
length vectors in the same way, and the output from one function is
easy to feed into the input of another.
License: MIT + file LICENSE
URL: https://stringr.tidyverse.org, https://github.com/tidyverse/stringr
BugReports: https://github.com/tidyverse/stringr/issues
Depends:
R (>= 3.3)
Imports:
cli,
glue (>= 1.6.1),
lifecycle (>= 1.0.3),
magrittr,
rlang (>= 1.0.0),
stringi (>= 1.5.3),
vctrs (>= 0.4.0)
Suggests:
covr,
dplyr,
gt,
htmltools,
htmlwidgets,
knitr,
rmarkdown,
testthat (>= 3.0.0),
tibble
VignetteBuilder:
knitr
Config/Needs/website: tidyverse/tidytemplate
Config/testthat/edition: 3
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.1

View file

@ -0,0 +1,3 @@
MissingColon
Whitespace:
Key:

View file

@ -0,0 +1,6 @@
License: MIT + file LICENSE
Description: A consistent, simple and easy to use set of wrappers around
the fantastic 'stringi' package. All function and argument names (and
positions) are consistent, all functions deal with "NA"'s and zero
length vectors in the same way, and the output from one function is
easy to feed into the input of another.

View file

@ -0,0 +1,8 @@
Key: value
Description: A consistent, simple and easy to use set of wrappers around
the fantastic 'stringi' package. All function and argument names (and
positions) are consistent, all functions deal with "NA"'s and zero
length vectors in the same way, and the output from one function is
easy to feed into the input of another.
License: MIT + file LICENSE

View file

@ -0,0 +1,2 @@
Version: 1.2.3
Description: a package with no name

View file

@ -0,0 +1 @@
Package: foo

View file

@ -0,0 +1,4 @@
Package: base
Version: 4.3.0
Suggests: methods
Built: R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix

View file

@ -23,6 +23,7 @@ const (
JavaScript Language = "javascript"
PHP Language = "php"
Python Language = "python"
R Language = "R"
Ruby Language = "ruby"
Rust Language = "rust"
Swift Language = "swift"
@ -41,6 +42,7 @@ var AllLanguages = []Language{
JavaScript,
PHP,
Python,
R,
Ruby,
Rust,
Swift,
@ -91,6 +93,8 @@ func LanguageByName(name string) Language {
// answer: no. We want this to definitively answer "which language does this package represent?"
// which might not be possible in all cases. See for more context: https://github.com/package-url/purl-spec/pull/178
return UnknownLanguage
case packageurl.TypeCran, "r":
return R
default:
return UnknownLanguage
}

View file

@ -66,6 +66,10 @@ func TestLanguageFromPURL(t *testing.T) {
purl: "pkg:hex/hpax/hpax@0.1.1",
want: UnknownLanguage,
},
{
purl: "pkg:cran/base@4.3.0",
want: R,
},
}
var languages []string
@ -231,6 +235,10 @@ func TestLanguageByName(t *testing.T) {
name: "haskell",
language: Haskell,
},
{
name: "R",
language: R,
},
}
for _, test := range tests {

View file

@ -38,6 +38,7 @@ const (
PythonPipfileLockMetadataType MetadataType = "PythonPipfileLockMetadata"
PythonRequirementsMetadataType MetadataType = "PythonRequirementsMetadata"
RebarLockMetadataType MetadataType = "RebarLockMetadataType"
RDescriptionFileMetadataType MetadataType = "RDescriptionFileMetadataType"
RpmMetadataType MetadataType = "RpmMetadata"
RustCargoPackageMetadataType MetadataType = "RustCargoPackageMetadata"
)
@ -69,6 +70,7 @@ var AllMetadataTypes = []MetadataType{
PythonPackageMetadataType,
PythonPipfileLockMetadataType,
PythonRequirementsMetadataType,
RDescriptionFileMetadataType,
RebarLockMetadataType,
RpmMetadataType,
RustCargoPackageMetadataType,
@ -101,6 +103,7 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
PythonPackageMetadataType: reflect.TypeOf(PythonPackageMetadata{}),
PythonPipfileLockMetadataType: reflect.TypeOf(PythonPipfileLockMetadata{}),
PythonRequirementsMetadataType: reflect.TypeOf(PythonRequirementsMetadata{}),
RDescriptionFileMetadataType: reflect.TypeOf(RDescriptionFileMetadata{}),
RebarLockMetadataType: reflect.TypeOf(RebarLockMetadata{}),
RpmMetadataType: reflect.TypeOf(RpmMetadata{}),
RustCargoPackageMetadataType: reflect.TypeOf(CargoPackageMetadata{}),

View file

@ -0,0 +1,21 @@
package pkg
type RDescriptionFileMetadata struct {
/*
Fields chosen by:
docker run --rm -it rocker/r-ver bash
$ install2.r ggplot2 # has a lot of dependencies
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep -v '^\s' | cut -d ':' -f 1 | sort | uniq -c | sort -nr
*/
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Author string `json:"author,omitempty"`
Maintainer string `json:"maintainer,omitempty"`
URL []string `json:"url,omitempty"`
Repository string `json:"repository,omitempty"`
Built string `json:"built,omitempty"`
NeedsCompilation bool `json:"needsCompilation,omitempty"`
Imports []string `json:"imports,omitempty"`
Depends []string `json:"depends,omitempty"`
Suggests []string `json:"suggests,omitempty"`
}

View file

@ -33,6 +33,7 @@ const (
PhpComposerPkg Type = "php-composer"
PortagePkg Type = "portage"
PythonPkg Type = "python"
Rpkg Type = "R-package"
RpmPkg Type = "rpm"
RustPkg Type = "rust-crate"
)
@ -61,6 +62,7 @@ var AllPkgs = []Type{
PhpComposerPkg,
PortagePkg,
PythonPkg,
Rpkg,
RpmPkg,
RustPkg,
}
@ -106,6 +108,8 @@ func (t Type) PackageURLType() string {
return "nix"
case NpmPkg:
return packageurl.TypeNPM
case Rpkg:
return packageurl.TypeCran
case RpmPkg:
return packageurl.TypeRPM
case RustPkg:
@ -173,6 +177,8 @@ func TypeByName(name string) Type {
return LinuxKernelModulePkg
case "nix":
return NixPkg
case packageurl.TypeCran:
return Rpkg
default:
return UnknownPkg
}

View file

@ -91,6 +91,10 @@ func TestTypeFromPURL(t *testing.T) {
purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
expected: NixPkg,
},
{
purl: "pkg:cran/base@4.3.0",
expected: Rpkg,
},
}
var pkgTypes []string

View file

@ -96,7 +96,7 @@ func TestPackagesCmdFlags(t *testing.T) {
name: "squashed-scope-flag",
args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage},
assertions: []traitAssertion{
assertPackageCount(35),
assertPackageCount(36),
assertSuccessfulReturnCode,
},
},
@ -213,7 +213,7 @@ func TestPackagesCmdFlags(t *testing.T) {
// the application config in the log matches that of what we expect to have been configured.
assertInOutput("parallelism: 2"),
assertInOutput("parallelism=2"),
assertPackageCount(35),
assertPackageCount(36),
assertSuccessfulReturnCode,
},
},
@ -224,7 +224,7 @@ func TestPackagesCmdFlags(t *testing.T) {
// the application config in the log matches that of what we expect to have been configured.
assertInOutput("parallelism: 1"),
assertInOutput("parallelism=1"),
assertPackageCount(35),
assertPackageCount(36),
assertSuccessfulReturnCode,
},
},
@ -238,7 +238,7 @@ func TestPackagesCmdFlags(t *testing.T) {
assertions: []traitAssertion{
assertNotInOutput("secret_password"),
assertNotInOutput("secret_key_path"),
assertPackageCount(35),
assertPackageCount(36),
assertSuccessfulReturnCode,
},
},

View file

@ -69,6 +69,14 @@ var imageOnlyTestCases = []testCase{
"joda-time": "2.9.2",
},
},
{
name: "find R packages",
pkgType: pkg.Rpkg,
pkgLanguage: pkg.R,
pkgInfo: map[string]string{
"base": "4.3.0",
},
},
}
var dirOnlyTestCases = []testCase{

View file

@ -220,10 +220,12 @@ func TestPkgCoverageDirectory(t *testing.T) {
observedLanguages.Remove(pkg.UnknownLanguage.String())
definedLanguages.Remove(pkg.UnknownLanguage.String())
definedLanguages.Remove(pkg.R.String())
observedPkgs.Remove(string(pkg.UnknownPkg))
definedPkgs.Remove(string(pkg.BinaryPkg))
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
definedPkgs.Remove(string(pkg.Rpkg))
definedPkgs.Remove(string(pkg.UnknownPkg))
// for directory scans we should not expect to see any of the following package types

View file

@ -0,0 +1,11 @@
Package: base
Version: 4.3.0
Priority: base
Title: The R Base Package
Author: R Core Team and contributors worldwide
Maintainer: R Core Team <do-use-Contact-address@r-project.org>
Contact: R-help mailing list <r-help@r-project.org>
Description: Base R functions.
License: Part of R 4.3.0
Suggests: methods
Built: R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix