blob: 347082fa1dc650998e6122fd062d023bebee8064 [file] [log] [blame] [edit]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package license
import (
"bytes"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
var (
update = flag.Bool("update", false, "If true, overwrite the LICENSE file with the new one.")
)
// TestLicense checks the extension's LICENSE file is up to date.
func TestLicense(t *testing.T) {
if testing.Short() {
t.Skip("TestLicense is skipped in short test mode")
}
extensionModuleRoot, err := moduleRoot("github.com/golang/vscode-go/extension")
if err != nil {
t.Fatal(err)
}
vscgoModuleRoot, err := moduleRoot("github.com/golang/vscode-go")
if err != nil {
t.Fatal(err)
}
// Check package.json is present in the extensionModuleRoot.
if _, err := os.Stat(filepath.Join(extensionModuleRoot, "package.json")); err != nil {
t.Fatalf("failed to find package.json in the github.com/golang/vscode-go/extension module root: %v", err)
}
// Checks below require to run tools (jsgl and yarn) using `npx`.
npx, err := exec.LookPath("npx")
if err != nil {
if *update {
t.Fatalf("this test requires npx but npx is missing: %v", err)
}
t.Skipf("license check requires npx: %v", err)
}
// Check if licenses of dependencies pass js-green-license check.
jsglCmd := exec.Command(npx, "--", "jsgl", "--local", ".")
jsglCmd.Dir = extensionModuleRoot
if output, err := jsglCmd.CombinedOutput(); err != nil {
t.Errorf("js-green-license check (%s) failed: %v\n%s", jsglCmd, err, output)
}
// Check if new and old LICENSE files agree.
newLICENSE, err := generateLicense(vscgoModuleRoot, extensionModuleRoot, npx)
if err != nil {
t.Fatalf("generating new LICENSE file failed: %v\nDid you run `npm ci` to install all necessary tools?", err)
}
if *update {
os.WriteFile(filepath.Join(extensionModuleRoot, "LICENSE"), newLICENSE, 0644)
return
}
oldLICENSE, err := os.ReadFile(filepath.Join(extensionModuleRoot, "LICENSE"))
if err != nil {
t.Fatalf("reading old LICENSE file failed: %v", err)
}
if diff := cmp.Diff(oldLICENSE, newLICENSE); diff != "" {
t.Fatalf("extension/LICENSE is outdated: run 'go generate' from extension/tools/license directory\ndiff: %s", diff)
}
}
func generateLicense(vscgoModuleRoot, extensionModuleRoot, npx string) (_ []byte, err error) {
// Extension's LICENSE file (extension/LICENSE) should contain
// - the vscode-go extension's own license (as in github.com/golang/vscode-go/LICENSE), and
// - the compiled license notices from its dependencies (yarn licenses generate-disclaimer --prod)
vscgoLICENSE, err := os.ReadFile(filepath.Join(vscgoModuleRoot, "LICENSE"))
if err != nil {
return nil, fmt.Errorf("reading vscode-go LICENSE file failed: %v", err)
}
// Remove yarn.lock if any - we use npm, so if there is yarn.lock that's likely
// a left-over file from license checking.
if err := os.Remove(filepath.Join(extensionModuleRoot, "yarn.lock")); err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to remove yarn.lock: %v", err)
}
// Convert npm package-lock.json to yarn.lock.
lockCmd := exec.Command(npx, "--", "yarn", "import")
lockCmd.Dir = extensionModuleRoot
if out, err := lockCmd.CombinedOutput(); err != nil {
return nil, fmt.Errorf("%s import failed: %v\n%s", lockCmd, err, out)
}
// Clean up yarn.lock.
defer func() {
if err2 := os.Remove(filepath.Join(extensionModuleRoot, "yarn.lock")); err2 != nil && err == nil {
err = fmt.Errorf("failed to remove yarn.lock: %v", err2)
}
}()
yarnCmd := exec.Command(npx, "--", "yarn", "licenses", "generate-disclaimer", "--prod")
yarnCmd.Dir = extensionModuleRoot
stderr := new(bytes.Buffer)
yarnCmd.Stderr = stderr
thirdPartyLicenses, err := yarnCmd.Output()
if err != nil {
return nil, fmt.Errorf("%s failed: %v\n%s", yarnCmd, err, stderr)
}
newLICENSE := new(bytes.Buffer)
fmt.Fprintf(newLICENSE, "%s\n\n%s", vscgoLICENSE, thirdPartyLicenses)
return newLICENSE.Bytes(), nil
}
func moduleRoot(module string) (string, error) {
out, err := exec.Command("go", "list", "-f", "{{.Dir}}", module).Output()
if err != nil {
return "", fmt.Errorf("failed to look up module root directory: %v", err)
}
return strings.TrimSpace(string(out)), nil
}