Ctrl K
ComunidadGuías…

licensing_shipping_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"testing"
)

// moduleRoot locates the repository root from this file's own path, so these
// tests do not depend on the working directory a runner happens to use.
func moduleRoot(t *testing.T) string {
	t.Helper()
	_, thisFile, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed; cannot locate the module root")
	}
	return filepath.Dir(thisFile)
}

func readRoot(t *testing.T, name string) string {
	t.Helper()
	b, err := os.ReadFile(filepath.Join(moduleRoot(t), name))
	if err != nil {
		t.Fatalf("read %s: %v", name, err)
	}
	return string(b)
}

// licenceFiles are the two documents every distribution channel must carry: the
// project's own licence, and the third-party notices for everything linked into
// the binary.
var licenceFiles = []string{"LICENSE", "THIRD-PARTY-NOTICES.md"}

// TestImagesShipTheLicenceFiles holds the container images to the same
// attribution the other channels already provide. The archives ship these two
// files beside the binary (.goreleaser.yaml archives.files) and the deb/rpm
// install them under /usr/share/doc/pingularity (nfpms.contents), but the images
// shipped neither for their whole history: both Dockerfiles COPY'd only the
// binary, and org.opencontainers.image.licenses="MIT" is a label declaring
// Pingularity's own licence, not a reproduction of anyone else's.
//
// That matters because an image is a binary distribution like any other, and the
// binary inside it carries BSD-3 code (golang.org/x/crypto, golang.org/x/sys,
// speedtest-go, the modernc musl-derived libc) plus an OFL font, each of which
// asks for its notice to travel with the distribution.
//
// The two halves are checked together on purpose. goreleaser stages only the
// built binaries into the docker build context, so a COPY without a matching
// extra_files entry fails the build - loudly, which is fine - while an
// extra_files entry without a COPY silently ships an image with no notices,
// which is the failure worth a test.
func TestImagesShipTheLicenceFiles(t *testing.T) {
	for _, dockerfile := range []string{"Dockerfile", "Dockerfile.iperf"} {
		body := readRoot(t, dockerfile)
		for _, want := range licenceFiles {
			// The COPY must name the file and land it under the conventional
			// package-documentation path, matching where the deb/rpm put it.
			re := regexp.MustCompile(`(?m)^COPY\b[^\n]*\b` + regexp.QuoteMeta(want) + `\b[^\n]*/usr/share/doc/pingularity/`)
			if !re.MatchString(body) {
				t.Errorf("%s: no COPY placing %s into /usr/share/doc/pingularity/ - the image would ship the binary with that attribution missing",
					dockerfile, want)
			}
		}
	}

	// Every dockers_v2 builder must stage both files into its build context.
	cfg := readRoot(t, ".goreleaser.yaml")
	dockersV2 := cfg[strings.Index(cfg, "\ndockers_v2:"):]
	if !strings.HasPrefix(dockersV2, "\ndockers_v2:") {
		t.Fatal(".goreleaser.yaml has no dockers_v2 block - this test's premise is gone")
	}
	builders := regexp.MustCompile(`(?m)^  - id: (\S+)`).FindAllStringSubmatch(dockersV2, -1)
	if len(builders) == 0 {
		t.Fatal("parsed no dockers_v2 builders - the test's matching has rotted")
	}
	for _, b := range builders {
		id := b[1]
		start := strings.Index(dockersV2, "  - id: "+id)
		end := len(dockersV2)
		if next := strings.Index(dockersV2[start+1:], "\n  - id: "); next >= 0 {
			end = start + 1 + next
		}
		block := dockersV2[start:end]
		for _, want := range licenceFiles {
			if !regexp.MustCompile(`(?m)^\s*-\s*` + regexp.QuoteMeta(want) + `\s*$`).MatchString(block) {
				t.Errorf("dockers_v2 builder %q does not list %s in extra_files - the Dockerfile's COPY of it will fail the build",
					id, want)
			}
		}
	}
}

// fontFace matches the dashboard's embedded @font-face: the family it declares,
// and the fact that the face is a data: URI rather than a fetched URL.
var fontFace = regexp.MustCompile(`@font-face\{font-family:'([^']+)'[^}]*src:url\(data:font/`)

// TestEmbeddedFontIsAttributed holds the notices to whatever font the dashboard
// actually embeds. internal/web/ui/index.html carries a base64 woff2 inside a
// data: URI, and web.go compiles that file into the binary with go:embed - so
// the font ships through every channel, in every image and package.
//
// Quicksand is under the SIL Open Font License, whose clause 2 requires that
// every copy of the Font Software carry the copyright notice and the licence.
// The font shipped for a long time with neither, because THIRD-PARTY-NOTICES.md
// was built from go.mod and a font is not a Go module - nothing in the toolchain
// could have noticed it.
//
// The family name is read out of the CSS rather than hard-coded here, so
// swapping the embedded face for a different one fails this test until its
// licence is reproduced too.
func TestEmbeddedFontIsAttributed(t *testing.T) {
	ui := readRoot(t, filepath.Join("internal", "web", "ui", "index.html"))
	m := fontFace.FindStringSubmatch(ui)
	if m == nil {
		// No embedded face is a legitimate state (a rewrite could drop it), but
		// it invalidates this test's premise, so say so rather than pass quietly.
		if strings.Contains(ui, "data:font/") {
			t.Fatal("index.html embeds a data: font this test's @font-face pattern no longer recognises - recheck the pattern")
		}
		t.Skip("the dashboard embeds no font")
	}
	family := m[1]
	notices := readRoot(t, "THIRD-PARTY-NOTICES.md")
	if !strings.Contains(notices, family) {
		t.Errorf("index.html embeds the %q font but THIRD-PARTY-NOTICES.md never names it - add a section reproducing its licence", family)
	}
	// The OFL is the licence Quicksand ships under; if the embedded face changes
	// to something under a different licence, the family check above fires first
	// and this line is updated with it.
	if !strings.Contains(notices, "SIL OPEN FONT LICENSE Version 1.1") {
		t.Errorf("THIRD-PARTY-NOTICES.md names %q but does not reproduce the SIL Open Font License, which that font requires to travel with every copy", family)
	}
}