// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package arch import ( "bytes" "io/fs" "os" "strings" "testing" "testing/fstest" "time" "forgejo.org/modules/packages" "github.com/mholt/archives" "github.com/stretchr/testify/require" ) func TestParsePackage(t *testing.T) { // Minimal PKGINFO contents and test FS const PKGINFO = `pkgname = a pkgbase = b pkgver = 1-2 arch = x86_64 ` mapfs := fstest.MapFS{ "pkginfo": &fstest.MapFile{ Data: []byte(PKGINFO), Mode: os.ModePerm, ModTime: time.Now(), }, "mtree": &fstest.MapFile{ Data: []byte("data"), Mode: os.ModePerm, ModTime: time.Now(), }, } // Test .PKGINFO file pinf, err := mapfs.Stat("pkginfo") require.NoError(t, err) // Test .MTREE file minf, err := mapfs.Stat("mtree") require.NoError(t, err) files := []archives.FileInfo{ { FileInfo: pinf, NameInArchive: ".PKGINFO", Open: func() (fs.File, error) { return mapfs.Open("pkginfo") }, }, { FileInfo: minf, NameInArchive: ".MTREE", Open: func() (fs.File, error) { return mapfs.Open("mtree") }, }, } t.Run("normal zst archive", func(t *testing.T) { var buf bytes.Buffer archive := archives.CompressedArchive{ Archival: archives.Tar{}, Compression: archives.Zstd{}, } archive.Archive(t.Context(), &buf, files) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) defer reader.Close() pkg, err := ParsePackage(reader) require.Equal(t, "zst", pkg.CompressType) require.Equal(t, "a", pkg.Name) require.Equal(t, "b", pkg.VersionMetadata.Base) require.Equal(t, "x86_64", pkg.FileMetadata.Arch) require.Equal(t, "1-2", pkg.Version) require.NoError(t, err) }) t.Run("normal xz archive", func(t *testing.T) { var buf bytes.Buffer archive := archives.CompressedArchive{ Archival: archives.Tar{}, Compression: archives.Xz{}, } archive.Archive(t.Context(), &buf, files) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) defer reader.Close() pkg, err := ParsePackage(reader) require.Equal(t, "xz", pkg.CompressType) require.Equal(t, "a", pkg.Name) require.Equal(t, "b", pkg.VersionMetadata.Base) require.Equal(t, "x86_64", pkg.FileMetadata.Arch) require.Equal(t, "1-2", pkg.Version) require.NoError(t, err) }) t.Run("normal gz archive", func(t *testing.T) { var buf bytes.Buffer archive := archives.CompressedArchive{ Archival: archives.Tar{}, Compression: archives.Gz{}, } archive.Archive(t.Context(), &buf, files) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) defer reader.Close() pkg, err := ParsePackage(reader) require.Equal(t, "gz", pkg.CompressType) require.Equal(t, "a", pkg.Name) require.Equal(t, "b", pkg.VersionMetadata.Base) require.Equal(t, "x86_64", pkg.FileMetadata.Arch) require.Equal(t, "1-2", pkg.Version) require.NoError(t, err) }) t.Run("missing .PKGINFO", func(t *testing.T) { var buf bytes.Buffer archive := archives.CompressedArchive{ Archival: archives.Tar{}, Compression: archives.Zstd{}, } archive.Archive(t.Context(), &buf, []archives.FileInfo{ { FileInfo: minf, NameInArchive: ".MTREE", Open: func() (fs.File, error) { return mapfs.Open("mtree") }, }, }) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) defer reader.Close() _, err = ParsePackage(reader) require.Error(t, err) require.Contains(t, err.Error(), ".PKGINFO file not found") }) t.Run("missing .MTREE", func(t *testing.T) { var buf bytes.Buffer archive := archives.CompressedArchive{ Archival: archives.Tar{}, Compression: archives.Zstd{}, } archive.Archive(t.Context(), &buf, []archives.FileInfo{ { FileInfo: pinf, NameInArchive: ".PKGINFO", Open: func() (fs.File, error) { return mapfs.Open("pkginfo") }, }, }) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) defer reader.Close() _, err = ParsePackage(reader) require.Error(t, err) require.Contains(t, err.Error(), ".MTREE file not found") }) } func TestParsePackageInfo(t *testing.T) { const PKGINFO = `# Generated by makepkg 6.0.2 # using fakeroot version 1.31 pkgname = a pkgbase = b pkgver = 1-2 pkgdesc = comment url = https://example.com/ group = group builddate = 3 packager = Name Surname size = 5 arch = x86_64 license = BSD provides = pvd depend = smth optdepend = hex checkdepend = ola makedepend = cmake backup = usr/bin/paket1 ` p, err := ParsePackageInfo("zst", strings.NewReader(PKGINFO)) require.NoError(t, err) require.Equal(t, Package{ CompressType: "zst", Name: "a", Version: "1-2", VersionMetadata: VersionMetadata{ Base: "b", Description: "comment", ProjectURL: "https://example.com/", Groups: []string{"group"}, Provides: []string{"pvd"}, License: []string{"BSD"}, Depends: []string{"smth"}, OptDepends: []string{"hex"}, MakeDepends: []string{"cmake"}, CheckDepends: []string{"ola"}, Backup: []string{"usr/bin/paket1"}, }, FileMetadata: FileMetadata{ InstalledSize: 5, BuildDate: 3, Packager: "Name Surname ", Arch: "x86_64", }, }, *p) } func TestValidatePackageSpec(t *testing.T) { newpkg := func() Package { return Package{ Name: "abc", Version: "1-1", VersionMetadata: VersionMetadata{ Base: "ghx", Description: "whoami", ProjectURL: "https://example.com/", Groups: []string{"gnome"}, Provides: []string{"abc", "def"}, License: []string{"GPL"}, Depends: []string{"go", "gpg=1", "curl>=3", "git<=7"}, OptDepends: []string{"git", "libgcc=1.0", "gzip>1.0", "gz>=1.0", "lz<1.0", "gzip<=1.0", "zstd>1.0:foo bar"}, MakeDepends: []string{"chrom"}, CheckDepends: []string{"bariy"}, Backup: []string{"etc/pacman.d/filo"}, }, FileMetadata: FileMetadata{ CompressedSize: 1, InstalledSize: 2, SHA256: "def", BuildDate: 3, Packager: "smon", Arch: "x86_64", }, } } t.Run("valid package", func(t *testing.T) { p := newpkg() err := ValidatePackageSpec(&p) require.NoError(t, err) }) t.Run("invalid package name", func(t *testing.T) { p := newpkg() p.Name = "!$%@^!*&()" err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid package name") }) t.Run("invalid package base", func(t *testing.T) { p := newpkg() p.VersionMetadata.Base = "!$%@^!*&()" err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid package base") }) t.Run("invalid package version", func(t *testing.T) { p := newpkg() p.VersionMetadata.Base = "una-luna?" err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid package base") }) t.Run("invalid package version", func(t *testing.T) { p := newpkg() p.Version = "una-luna" err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid package version") }) t.Run("missing architecture", func(t *testing.T) { p := newpkg() p.FileMetadata.Arch = "" err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "architecture should be specified") }) t.Run("invalid URL", func(t *testing.T) { p := newpkg() p.VersionMetadata.ProjectURL = "http%%$#" err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid project URL") }) t.Run("invalid check dependency", func(t *testing.T) { p := newpkg() p.VersionMetadata.CheckDepends = []string{"Err^_^"} err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid check dependency") }) t.Run("invalid dependency", func(t *testing.T) { p := newpkg() p.VersionMetadata.Depends = []string{"^^abc"} err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid dependency") }) t.Run("invalid make dependency", func(t *testing.T) { p := newpkg() p.VersionMetadata.MakeDepends = []string{"^m^"} err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid make dependency") }) t.Run("invalid provides", func(t *testing.T) { p := newpkg() p.VersionMetadata.Provides = []string{"^m^"} err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid provides") }) t.Run("invalid optional dependency", func(t *testing.T) { p := newpkg() p.VersionMetadata.OptDepends = []string{"^m^:MM"} err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid optional dependency") }) t.Run("invalid optional dependency", func(t *testing.T) { p := newpkg() p.VersionMetadata.Backup = []string{"/ola/cola"} err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "backup file contains leading forward slash") }) } func TestDescAndFileString(t *testing.T) { const pkgDesc = `%FILENAME% zstd-1.5.5-1-x86_64.pkg.tar.zst %NAME% zstd %BASE% zstd %VERSION% 1.5.5-1 %DESC% Zstandard - Fast real-time compression algorithm %GROUPS% dummy1 dummy2 %CSIZE% 401 %ISIZE% 1500453 %MD5SUM% 5016660ef3d9aa148a7b72a08d3df1b2 %SHA256SUM% 9fa4ede47e35f5971e4f26ecadcbfb66ab79f1d638317ac80334a3362dedbabd %URL% https://facebook.github.io/zstd/ %LICENSE% BSD GPL2 %ARCH% x86_64 %BUILDDATE% 1681646714 %PACKAGER% Jelle van der Waa %PROVIDES% libzstd.so=1-64 %DEPENDS% glibc gcc-libs zlib xz lz4 %OPTDEPENDS% dummy3 dummy4 %MAKEDEPENDS% cmake gtest ninja %CHECKDEPENDS% dummy5 dummy6 ` const pkgFiles = `%FILES% usr/ usr/bin/ usr/bin/zstd ` md := &Package{ CompressType: "zst", Name: "zstd", Version: "1.5.5-1", VersionMetadata: VersionMetadata{ Base: "zstd", Description: "Zstandard - Fast real-time compression algorithm", ProjectURL: "https://facebook.github.io/zstd/", Groups: []string{"dummy1", "dummy2"}, Provides: []string{"libzstd.so=1-64"}, License: []string{"BSD", "GPL2"}, Depends: []string{"glibc", "gcc-libs", "zlib", "xz", "lz4"}, OptDepends: []string{"dummy3", "dummy4"}, MakeDepends: []string{"cmake", "gtest", "ninja"}, CheckDepends: []string{"dummy5", "dummy6"}, }, FileMetadata: FileMetadata{ CompressedSize: 401, InstalledSize: 1500453, MD5: "5016660ef3d9aa148a7b72a08d3df1b2", SHA256: "9fa4ede47e35f5971e4f26ecadcbfb66ab79f1d638317ac80334a3362dedbabd", BuildDate: 1681646714, Packager: "Jelle van der Waa ", Arch: "x86_64", Files: []string{"usr/", "usr/bin/", "usr/bin/zstd"}, }, } require.Equal(t, pkgDesc, md.Desc()) require.Equal(t, pkgFiles, md.Files()) }