internal: add GetLicenses by path

This change introduces a new method on the DataSource interface,
GetLicenses, which returns the licenses that apply for a given path,
module path, and resolved version combination.
A license in the current or any parent directory of the specified path
applies to it.

Fixes golang/go#40027

Change-Id: If91429ac12880ac3b6254bf0acd7d3ac983c93e7
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/241718
Reviewed-by: Julie Qiu <julie@golang.org>
This commit is contained in:
Amarjeet Anand 2020-07-15 06:01:17 +05:30 коммит произвёл Julie Qiu
Родитель 130a4e343b
Коммит f7303dcc15
5 изменённых файлов: 246 добавлений и 3 удалений

Просмотреть файл

@ -21,6 +21,8 @@ type DataSource interface {
// GetImports returns a slice of import paths imported by the package
// specified by path and version.
GetImports(ctx context.Context, pkgPath, modulePath, version string) ([]string, error)
// GetLicenses returns licenses at the given path for given modulePath and version.
GetLicenses(ctx context.Context, fullPath, modulePath, resolvedVersion string) ([]*licenses.License, error)
// GetModuleInfo returns the ModuleInfo corresponding to modulePath and
// version.
GetModuleInfo(ctx context.Context, modulePath, version string) (*ModuleInfo, error)

Просмотреть файл

@ -8,6 +8,7 @@ import (
"context"
"database/sql"
"fmt"
"path"
"reflect"
"sort"
"strings"
@ -17,6 +18,61 @@ import (
"golang.org/x/pkgsite/internal/licenses"
)
// GetLicenses returns the licenses that applies to the fullPath for the given module version.
// It returns an InvalidArgument error if the module path or version is invalid.
func (db *DB) GetLicenses(ctx context.Context, fullPath, modulePath, resolvedVersion string) (_ []*licenses.License, err error) {
defer derrors.Wrap(&err, "GetLicenses(ctx, %q, %q, %q)", fullPath, modulePath, resolvedVersion)
if fullPath == "" || resolvedVersion == "" {
return nil, fmt.Errorf("neither fullpath nor resolvedVersion can be empty: %w", derrors.InvalidArgument)
}
query := `
SELECT
l.types,
l.file_path,
l.contents,
l.coverage
FROM
licenses l
INNER JOIN
paths p
ON
p.module_id=l.module_id
INNER JOIN
modules m
ON
p.module_id=m.id
WHERE
p.path = $1
AND m.module_path = $2
AND m.version = $3;`
rows, err := db.db.Query(ctx, query, fullPath, modulePath, resolvedVersion)
if err != nil {
return nil, err
}
defer rows.Close()
moduleLicenses, err := collectLicenses(rows)
if err != nil {
return nil, err
}
var lics []*licenses.License
// The `query` returns all licenses for the module version. We need to
// filter the licenses that applies to the specified fullPath, i.e.
// A license in the current or any parent directory of the specified
// fullPath applies to it.
for _, license := range moduleLicenses {
licensePath := path.Join(modulePath, path.Dir(license.FilePath))
if strings.HasPrefix(fullPath, licensePath) {
lics = append(lics, license)
}
}
return lics, nil
}
// LegacyGetModuleLicenses returns all licenses associated with the given module path and
// version. These are the top-level licenses in the module zip file.
// It returns an InvalidArgument error if the module path or version is invalid.

Просмотреть файл

@ -6,13 +6,83 @@ package postgres
import (
"context"
"errors"
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/licenses"
"golang.org/x/pkgsite/internal/testing/sample"
)
func TestGetLicenses(t *testing.T) {
testModule := sample.Module(sample.ModulePath, "v1.2.3", "A/B")
mit := &licenses.Metadata{Types: []string{"MIT"}, FilePath: "LICENSE"}
bsd := &licenses.Metadata{Types: []string{"BSD-3-Clause"}, FilePath: "A/B/LICENSE"}
mitLicense := &licenses.License{Metadata: mit}
bsdLicense := &licenses.License{Metadata: bsd}
testModule.Licenses = []*licenses.License{bsdLicense, mitLicense}
sort.Slice(testModule.Directories, func(i, j int) bool {
return testModule.Directories[i].Path < testModule.Directories[j].Path
})
// github.com/valid/module_name
testModule.Directories[0].Licenses = []*licenses.Metadata{mit}
// github.com/valid/module_name/A
testModule.Directories[1].Licenses = []*licenses.Metadata{mit}
// github.com/valid/module_name/A/B
testModule.Directories[2].Licenses = []*licenses.Metadata{mit, bsd}
tests := []struct {
err error
name string
fullPath string
want []*licenses.License
}{
{name: "empty path", err: derrors.InvalidArgument},
{name: "module root", fullPath: sample.ModulePath, want: []*licenses.License{testModule.Licenses[1]}},
{name: "package without license", fullPath: sample.ModulePath + "/A", want: []*licenses.License{testModule.Licenses[1]}},
{name: "package with additional license", fullPath: sample.ModulePath + "/A/B", want: testModule.Licenses},
}
defer ResetTestDB(testDB, t)
ctx, cancel := context.WithTimeout(context.Background(), testTimeout*5)
defer cancel()
if err := testDB.InsertModule(ctx, testModule); err != nil {
t.Fatal(err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := testDB.GetLicenses(ctx, test.fullPath, sample.ModulePath, testModule.Version)
if !errors.Is(err, test.err) {
t.Fatal(err)
}
sort.Slice(got, func(i, j int) bool {
return got[i].FilePath < got[j].FilePath
})
sort.Slice(test.want, func(i, j int) bool {
return test.want[i].FilePath < test.want[j].FilePath
})
for i := range got {
sort.Strings(got[i].Types)
}
for i := range test.want {
sort.Strings(test.want[i].Types)
}
cmpopt := cmpopts.IgnoreFields(licenses.License{}, "Contents")
if diff := cmp.Diff(test.want, got, cmpopt); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestLegacyGetModuleLicenses(t *testing.T) {
modulePath := "test.module"
testModule := sample.Module(modulePath, "v1.2.3", "", "foo", "bar")

Просмотреть файл

@ -117,6 +117,33 @@ func (ds *DataSource) GetImports(ctx context.Context, pkgPath, modulePath, versi
return vp.Imports, nil
}
// GetLicenses return licenses at path for the given module path and version.
func (ds *DataSource) GetLicenses(ctx context.Context, fullPath, modulePath, resolvedVersion string) (_ []*licenses.License, err error) {
defer derrors.Wrap(&err, "GetLicenses(%q, %q, %q)", fullPath, modulePath, resolvedVersion)
v, err := ds.getModule(ctx, modulePath, resolvedVersion)
if err != nil {
return nil, err
}
var lics []*licenses.License
// ds.getModule() returns all licenses for the module version. We need to
// filter the licenses that applies to the specified fullPath, i.e.
// A license in the current or any parent directory of the specified
// fullPath applies to it.
for _, license := range v.Licenses {
licensePath := path.Join(modulePath, path.Dir(license.FilePath))
if strings.HasPrefix(fullPath, licensePath) {
lics = append(lics, license)
}
}
if len(lics) == 0 {
return nil, fmt.Errorf("path %s is missing from module %s: %w", fullPath, modulePath, derrors.NotFound)
}
return lics, nil
}
// LegacyGetModuleLicenses returns root-level licenses detected within the module zip
// for modulePath and version.
func (ds *DataSource) LegacyGetModuleLicenses(ctx context.Context, modulePath, version string) (_ []*licenses.License, err error) {

Просмотреть файл

@ -6,12 +6,16 @@ package proxydatasource
import (
"context"
"errors"
"sort"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/licensecheck"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/licenses"
"golang.org/x/pkgsite/internal/proxy"
"golang.org/x/pkgsite/internal/testing/sample"
@ -49,6 +53,28 @@ func setup(t *testing.T) (context.Context, *DataSource, func()) {
var (
wantLicenseMD = sample.LicenseMetadata[0]
wantLicense = &licenses.License{Metadata: wantLicenseMD}
wantLicenseMIT = &licenses.License{
Metadata: &licenses.Metadata{
Types: []string{"MIT"},
FilePath: "LICENSE",
Coverage: licensecheck.Coverage{
Percent: 100,
Match: []licensecheck.Match{{Name: "MIT", Type: licensecheck.MIT, Percent: 100, End: 1049}},
},
},
Contents: []byte(testhelper.MITLicense),
}
wantLicenseBSD = &licenses.License{
Metadata: &licenses.Metadata{
Types: []string{"BSD-0-Clause"},
FilePath: "qux/LICENSE",
Coverage: licensecheck.Coverage{
Percent: 100,
Match: []licensecheck.Match{{Name: "BSD-0-Clause", Type: licensecheck.BSD, Percent: 100, End: 633}},
},
},
Contents: []byte(testhelper.BSD0License),
}
wantPackage = internal.LegacyPackage{
Path: "foo.com/bar/baz",
Name: "baz",
@ -144,6 +170,68 @@ func TestDataSource_GetModuleInfo(t *testing.T) {
}
}
func TestDataSource_GetLicenses(t *testing.T) {
t.Helper()
testModules := []*proxy.TestModule{
{
ModulePath: "foo.com/bar",
Version: "v1.1.0",
Files: map[string]string{
"go.mod": "module foo.com/bar",
"LICENSE": testhelper.MITLicense,
"bar.go": "//Package bar provides a helpful constant.\npackage bar\nimport \"net/http\"\nconst OK = http.StatusOK",
"baz/baz.go": "//Package baz provides a helpful constant.\npackage baz\nimport \"net/http\"\nconst OK = http.StatusOK",
"qux/LICENSE": testhelper.BSD0License,
"qux/qux.go": "//Package qux provides a helpful constant.\npackage qux\nimport \"net/http\"\nconst OK = http.StatusOK",
},
},
}
client, teardownProxy := proxy.SetupTestProxy(t, testModules)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
ds := New(client)
teardown := func() {
teardownProxy()
cancel()
}
defer teardown()
tests := []struct {
err error
name string
fullPath string
modulePath string
want []*licenses.License
}{
{name: "no license dir", fullPath: "foo.com", modulePath: "foo.com/bar", err: derrors.NotFound},
{name: "invalid dir", fullPath: "foo.com/invalid", modulePath: "foo.com/invalid", err: derrors.NotFound},
{name: "root dir", fullPath: "foo.com/bar", modulePath: "foo.com/bar", want: []*licenses.License{wantLicenseMIT}},
{name: "package with no extra license", fullPath: "foo.com/bar/baz", modulePath: "foo.com/bar", want: []*licenses.License{wantLicenseMIT}},
{name: "package with additional license", fullPath: "foo.com/bar/qux", modulePath: "foo.com/bar", want: []*licenses.License{wantLicenseMIT, wantLicenseBSD}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := ds.GetLicenses(ctx, test.fullPath, test.modulePath, "v1.1.0")
if !errors.Is(err, test.err) {
t.Fatal(err)
}
sort.Slice(got, func(i, j int) bool {
return got[i].FilePath < got[j].FilePath
})
sort.Slice(test.want, func(i, j int) bool {
return test.want[i].FilePath < test.want[j].FilePath
})
if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("GetLicenses diff (-want +got):\n%s", diff)
}
})
}
}
func TestDataSource_LegacyGetModuleLicenses(t *testing.T) {
ctx, ds, teardown := setup(t)
defer teardown()