mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-09 05:38:54 +09:00
e2a0a87ae0
Backport #38788 by @terriblegoodday Nested `Package.swift` files overwrote the real package manifest. The parser matched on the base name and kept the last entry in ZIP order. `apple/swift-collections` ships `Benchmarks/Package.swift` and `Utils/Debugger/FormatterFixtures/Package.swift`. The latter sorts after the root `Package.swift`, so the registry stored a fixture manifest with the wrong `swift-tools-version`, causing a toolchain mismatch and a failed build. GRDB, swift-markdown, swift-syntax, sentry-cocoa and SDWebImage share this layout. The parser now keeps only manifests from the shallowest directory holding one. That covers both a package at the archive root and the single top level directory `swift package archive-source` produces. At equal depth the first directory by name wins, so an archive always yields the same metadata. A nested manifest above the size limit no longer rejects the upload. Assisted by Claude Opus 5 and Claude Fable. Co-authored-by: Eduard Dzhumagaliev <ed_dzhumagaliev@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
311 lines
9.8 KiB
Go
311 lines
9.8 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package swift
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dev/modules/test"
|
|
|
|
"github.com/hashicorp/go-version"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
packageName = "gitea"
|
|
packageVersion = "1.0.1"
|
|
packageDescription = "Package Description"
|
|
packageRepositoryURL = "https://gitea.io/gitea/gitea"
|
|
packageLicenseURL = "https://opensource.org/license/mit"
|
|
packageAuthor = "KN4CK3R"
|
|
packageLicense = "MIT"
|
|
)
|
|
|
|
// writeOrderedZipArchive writes name/content pairs in the given order, which map based test.WriteZipArchive cannot do
|
|
func writeOrderedZipArchive(entries [][2]string) *bytes.Buffer {
|
|
buf := &bytes.Buffer{}
|
|
zw := zip.NewWriter(buf)
|
|
for _, entry := range entries {
|
|
w, _ := zw.Create(entry[0])
|
|
_, _ = w.Write([]byte(entry[1]))
|
|
}
|
|
_ = zw.Close()
|
|
return buf
|
|
}
|
|
|
|
func TestParsePackage(t *testing.T) {
|
|
t.Run("MissingManifestFile", func(t *testing.T) {
|
|
data := test.WriteZipArchive(map[string]string{"dummy.txt": ""})
|
|
p, err := ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), nil)
|
|
assert.Nil(t, p)
|
|
assert.ErrorIs(t, err, ErrMissingManifestFile)
|
|
})
|
|
|
|
t.Run("ManifestFileTooLarge", func(t *testing.T) {
|
|
data := test.WriteZipArchive(map[string]string{
|
|
"Package.swift": strings.Repeat("a", maxManifestFileSize+1),
|
|
})
|
|
p, err := ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), nil)
|
|
assert.Nil(t, p)
|
|
assert.ErrorIs(t, err, ErrManifestFileTooLarge)
|
|
})
|
|
|
|
t.Run("WithoutMetadata", func(t *testing.T) {
|
|
content1 := "// swift-tools-version:5.7\n//\n// Package.swift"
|
|
content2 := "// swift-tools-version:5.6\n//\n// Package@swift-5.6.swift"
|
|
|
|
data := test.WriteZipArchive(map[string]string{
|
|
"Package.swift": content1,
|
|
"Package@swift-5.5.swift": content2,
|
|
})
|
|
|
|
p, err := ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), nil)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotNil(t, p.Metadata)
|
|
assert.Empty(t, p.RepositoryURLs)
|
|
assert.Len(t, p.Metadata.Manifests, 2)
|
|
m := p.Metadata.Manifests[""]
|
|
assert.Equal(t, "5.7", m.ToolsVersion)
|
|
assert.Equal(t, content1, m.Content)
|
|
m = p.Metadata.Manifests["5.5"]
|
|
assert.Equal(t, "5.6", m.ToolsVersion)
|
|
assert.Equal(t, content2, m.Content)
|
|
})
|
|
|
|
t.Run("IgnoresNestedManifests", func(t *testing.T) {
|
|
rootManifest := "// swift-tools-version:5.7\n//\n// Package.swift"
|
|
rootAltManifest := "// swift-tools-version:5.5\n//\n// Package@swift-5.5.swift"
|
|
rootPatchAltManifest := "// swift-tools-version:5.7.1\n//\n// Package@swift-5.7.1.swift"
|
|
nestedManifest := "// swift-tools-version:6.3\n//\n// nested fixture package"
|
|
|
|
data := writeOrderedZipArchive([][2]string{
|
|
{"Package.swift", rootManifest},
|
|
{"Package@swift-5.5.swift", rootAltManifest},
|
|
{"Package@swift-5.7.1.swift", rootPatchAltManifest},
|
|
{"Benchmarks/Package.swift", nestedManifest},
|
|
{"Utils/Fixtures/PlainPackage/Package.swift", nestedManifest},
|
|
})
|
|
|
|
p, err := ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), nil)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, p.Metadata.Manifests, 3)
|
|
assert.Equal(t, rootManifest, p.Metadata.Manifests[""].Content)
|
|
assert.Equal(t, "5.7", p.Metadata.Manifests[""].ToolsVersion)
|
|
assert.Equal(t, rootAltManifest, p.Metadata.Manifests["5.5"].Content)
|
|
assert.Equal(t, rootPatchAltManifest, p.Metadata.Manifests["5.7.1"].Content)
|
|
})
|
|
|
|
t.Run("IgnoresNestedManifestsInPrefixedArchive", func(t *testing.T) {
|
|
rootManifest := "// swift-tools-version:5.7\n//\n// Package.swift"
|
|
|
|
// `swift package archive-source` produces archives with a single top level directory
|
|
data := writeOrderedZipArchive([][2]string{
|
|
{"gitea-1.0.1/Package.swift", rootManifest},
|
|
{"gitea-1.0.1/Tests/Fixtures/Package.swift", "// swift-tools-version:6.3"},
|
|
})
|
|
|
|
p, err := ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), nil)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, p.Metadata.Manifests, 1)
|
|
assert.Equal(t, rootManifest, p.Metadata.Manifests[""].Content)
|
|
})
|
|
|
|
t.Run("AltManifestOnlyInRootDirectory", func(t *testing.T) {
|
|
// a deeper Package.swift belongs to a nested package and must not stand in for the missing root manifest
|
|
data := test.WriteZipArchive(map[string]string{
|
|
"Package@swift-5.5.swift": "// swift-tools-version:5.5",
|
|
"Sub/Package.swift": "// swift-tools-version:5.7",
|
|
})
|
|
|
|
p, err := ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), nil)
|
|
assert.Nil(t, p)
|
|
assert.ErrorIs(t, err, ErrMissingManifestFile)
|
|
})
|
|
|
|
t.Run("ManifestDirectoryTieBreak", func(t *testing.T) {
|
|
contentA := "// swift-tools-version:5.7\n// A"
|
|
contentB := "// swift-tools-version:5.7\n// B"
|
|
|
|
// at equal depth the name decides, never the archive order
|
|
data := writeOrderedZipArchive([][2]string{
|
|
{"a/Package.swift", contentA},
|
|
{"b/Package.swift", contentB},
|
|
})
|
|
|
|
p, err := ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), nil)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, p.Metadata.Manifests, 1)
|
|
assert.Equal(t, contentA, p.Metadata.Manifests[""].Content)
|
|
})
|
|
|
|
t.Run("WithMetadata", func(t *testing.T) {
|
|
data := test.WriteZipArchive(map[string]string{
|
|
"Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
|
|
})
|
|
|
|
p, err := ParsePackage(
|
|
bytes.NewReader(data.Bytes()), int64(data.Len()),
|
|
strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","keywords":["swift","package"],"license":"`+packageLicense+`","licenseURL":"`+packageLicenseURL+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`),
|
|
)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotNil(t, p.Metadata)
|
|
assert.Len(t, p.Metadata.Manifests, 1)
|
|
m := p.Metadata.Manifests[""]
|
|
assert.Equal(t, "5.7", m.ToolsVersion)
|
|
|
|
assert.Equal(t, packageDescription, p.Metadata.Description)
|
|
assert.ElementsMatch(t, []string{"swift", "package"}, p.Metadata.Keywords)
|
|
assert.Equal(t, packageLicense, p.Metadata.License)
|
|
assert.Equal(t, packageLicenseURL, p.Metadata.LicenseURL)
|
|
assert.Equal(t, packageAuthor, p.Metadata.Author.Name)
|
|
assert.Equal(t, packageAuthor, p.Metadata.Author.GivenName)
|
|
assert.Equal(t, packageRepositoryURL, p.Metadata.RepositoryURL)
|
|
assert.ElementsMatch(t, []string{packageRepositoryURL}, p.RepositoryURLs)
|
|
})
|
|
|
|
t.Run("WithExplicitNameField", func(t *testing.T) {
|
|
data := test.WriteZipArchive(map[string]string{
|
|
"Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
|
|
})
|
|
|
|
authorName := "John Doe"
|
|
p, err := ParsePackage(
|
|
bytes.NewReader(data.Bytes()), int64(data.Len()),
|
|
strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","author":{"name":"`+authorName+`","givenName":"John","familyName":"Doe"}}`),
|
|
)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, authorName, p.Metadata.Author.Name)
|
|
assert.Equal(t, "John", p.Metadata.Author.GivenName)
|
|
assert.Equal(t, "Doe", p.Metadata.Author.FamilyName)
|
|
})
|
|
|
|
t.Run("WithEmptyJSONMetadata", func(t *testing.T) {
|
|
data := test.WriteZipArchive(map[string]string{
|
|
"Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
|
|
})
|
|
|
|
p, err := ParsePackage(
|
|
bytes.NewReader(data.Bytes()), int64(data.Len()),
|
|
strings.NewReader(`{}`),
|
|
)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, p.Metadata)
|
|
assert.Empty(t, p.Metadata.Author.Name)
|
|
assert.Empty(t, p.RepositoryURLs)
|
|
})
|
|
|
|
t.Run("NameFieldGeneration", func(t *testing.T) {
|
|
data := test.WriteZipArchive(map[string]string{
|
|
"Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
|
|
})
|
|
|
|
// Test with only individual name components - Name should be auto-generated
|
|
p, err := ParsePackage(
|
|
bytes.NewReader(data.Bytes()), int64(data.Len()),
|
|
strings.NewReader(`{"author":{"givenName":"John","middleName":"Q","familyName":"Doe"}}`),
|
|
)
|
|
assert.NotNil(t, p)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "John Q Doe", p.Metadata.Author.Name)
|
|
assert.Equal(t, "John", p.Metadata.Author.GivenName)
|
|
assert.Equal(t, "Q", p.Metadata.Author.MiddleName)
|
|
assert.Equal(t, "Doe", p.Metadata.Author.FamilyName)
|
|
})
|
|
}
|
|
|
|
func TestTrimmedVersionString(t *testing.T) {
|
|
cases := []struct {
|
|
Version *version.Version
|
|
Expected string
|
|
}{
|
|
{
|
|
Version: version.Must(version.NewVersion("1")),
|
|
Expected: "1.0",
|
|
},
|
|
{
|
|
Version: version.Must(version.NewVersion("1.0")),
|
|
Expected: "1.0",
|
|
},
|
|
{
|
|
Version: version.Must(version.NewVersion("1.0.0")),
|
|
Expected: "1.0",
|
|
},
|
|
{
|
|
Version: version.Must(version.NewVersion("1.0.1")),
|
|
Expected: "1.0.1",
|
|
},
|
|
{
|
|
Version: version.Must(version.NewVersion("1.0+meta")),
|
|
Expected: "1.0",
|
|
},
|
|
{
|
|
Version: version.Must(version.NewVersion("1.0.0+meta")),
|
|
Expected: "1.0",
|
|
},
|
|
{
|
|
Version: version.Must(version.NewVersion("1.0.1+meta")),
|
|
Expected: "1.0.1",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
assert.Equal(t, c.Expected, TrimmedVersionString(c.Version))
|
|
}
|
|
}
|
|
|
|
func TestPersonNameString(t *testing.T) {
|
|
cases := []struct {
|
|
Name string
|
|
Person Person
|
|
Expected string
|
|
}{
|
|
{
|
|
Name: "GivenNameOnly",
|
|
Person: Person{GivenName: "John"},
|
|
Expected: "John",
|
|
},
|
|
{
|
|
Name: "GivenAndFamily",
|
|
Person: Person{GivenName: "John", FamilyName: "Doe"},
|
|
Expected: "John Doe",
|
|
},
|
|
{
|
|
Name: "FullName",
|
|
Person: Person{GivenName: "John", MiddleName: "Q", FamilyName: "Doe"},
|
|
Expected: "John Q Doe",
|
|
},
|
|
{
|
|
Name: "MiddleAndFamily",
|
|
Person: Person{MiddleName: "Q", FamilyName: "Doe"},
|
|
Expected: "Q Doe",
|
|
},
|
|
{
|
|
Name: "Empty",
|
|
Person: Person{},
|
|
Expected: "",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
assert.Equal(t, c.Expected, c.Person.String())
|
|
})
|
|
}
|
|
}
|