mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-07-15 11:57:59 +00:00
Add comprehensive CI/CD improvements and test coverage
This commit introduces major enhancements to the CI/CD pipeline and testing infrastructure: CI/CD Improvements: - Consolidated modern and legacy CI workflows into a single comprehensive pipeline - Removed all publishing functionality from CI (no longer needed) - Added 8 new advanced testing jobs for pull requests: * advanced-coverage: Detailed coverage analysis with base branch comparison * performance-profile: CPU and memory profiling with benchmarks * fuzz-test: Automated fuzz testing for supported packages * deep-analysis: Multiple static analysis tools (shadow, ineffassign, gosec, staticcheck) * mutation-test: Tests effectiveness of test suite on changed files * dependency-audit: Security vulnerabilities and outdated dependency checks * stress-test: Race detection with 100 iterations and parallel testing * test-report-summary: Aggregates all reports into a single PR comment - Enabled RUN_LONG_TESTS by default for thorough testing - Added automatic PR comment generation with all test results Testing Infrastructure: - Added comprehensive test files across all packages to improve coverage - Implemented unit tests for previously untested packages - Added race condition tests for concurrent operations - Created integration tests for API endpoints - Added storage backend tests (etcd, goleveldb) - Implemented command-line interface tests Local Testing Support: - Added act configuration for testing GitHub Actions locally - Created docker-compose.ci.yml for full CI environment simulation - Updated CONTRIBUTING.md with detailed local testing instructions Documentation Updates: - Added comprehensive CI documentation to CONTRIBUTING.md - Removed obsolete references to Travis CI - Updated Go version requirements to 1.24 - Added act usage instructions and examples Other Improvements: - Updated .gitignore to exclude coverage reports and build artifacts - Added test-act.yml workflow for testing act functionality - Created CI_SUMMARY.md documenting all CI capabilities These changes transform aptly's CI from a basic testing pipeline into a comprehensive quality assurance system that provides immediate feedback on code quality, performance, security, and test effectiveness.
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type CollectionsSuite struct {
|
||||
db database.Storage
|
||||
factory *CollectionFactory
|
||||
}
|
||||
|
||||
var _ = Suite(&CollectionsSuite{})
|
||||
|
||||
func (s *CollectionsSuite) SetUpTest(c *C) {
|
||||
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
||||
s.factory = NewCollectionFactory(s.db)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestNewCollectionFactory(c *C) {
|
||||
factory := NewCollectionFactory(s.db)
|
||||
c.Check(factory, NotNil)
|
||||
c.Check(factory.db, Equals, s.db)
|
||||
c.Check(factory.Mutex, NotNil)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestTemporaryDB(c *C) {
|
||||
tempDB, err := s.factory.TemporaryDB()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(tempDB, NotNil)
|
||||
|
||||
// Clean up
|
||||
tempDB.Close()
|
||||
tempDB.Drop()
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestPackageCollection(c *C) {
|
||||
// First call creates the collection
|
||||
collection1 := s.factory.PackageCollection()
|
||||
c.Check(collection1, NotNil)
|
||||
|
||||
// Second call returns the same instance
|
||||
collection2 := s.factory.PackageCollection()
|
||||
c.Check(collection2, Equals, collection1)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestRemoteRepoCollection(c *C) {
|
||||
// First call creates the collection
|
||||
collection1 := s.factory.RemoteRepoCollection()
|
||||
c.Check(collection1, NotNil)
|
||||
|
||||
// Second call returns the same instance
|
||||
collection2 := s.factory.RemoteRepoCollection()
|
||||
c.Check(collection2, Equals, collection1)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestSnapshotCollection(c *C) {
|
||||
// First call creates the collection
|
||||
collection1 := s.factory.SnapshotCollection()
|
||||
c.Check(collection1, NotNil)
|
||||
|
||||
// Second call returns the same instance
|
||||
collection2 := s.factory.SnapshotCollection()
|
||||
c.Check(collection2, Equals, collection1)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestLocalRepoCollection(c *C) {
|
||||
// First call creates the collection
|
||||
collection1 := s.factory.LocalRepoCollection()
|
||||
c.Check(collection1, NotNil)
|
||||
|
||||
// Second call returns the same instance
|
||||
collection2 := s.factory.LocalRepoCollection()
|
||||
c.Check(collection2, Equals, collection1)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestPublishedRepoCollection(c *C) {
|
||||
// First call creates the collection
|
||||
collection1 := s.factory.PublishedRepoCollection()
|
||||
c.Check(collection1, NotNil)
|
||||
|
||||
// Second call returns the same instance
|
||||
collection2 := s.factory.PublishedRepoCollection()
|
||||
c.Check(collection2, Equals, collection1)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestChecksumCollectionWithNilDB(c *C) {
|
||||
// First call with nil DB creates the collection
|
||||
collection1 := s.factory.ChecksumCollection(nil)
|
||||
c.Check(collection1, NotNil)
|
||||
|
||||
// Second call with nil DB returns the same instance
|
||||
collection2 := s.factory.ChecksumCollection(nil)
|
||||
c.Check(collection2, Equals, collection1)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestChecksumCollectionWithDB(c *C) {
|
||||
// Create temporary DB
|
||||
tempDB, err := s.factory.TemporaryDB()
|
||||
c.Check(err, IsNil)
|
||||
defer tempDB.Close()
|
||||
defer tempDB.Drop()
|
||||
|
||||
// Call with specific DB creates new collection
|
||||
collection1 := s.factory.ChecksumCollection(tempDB)
|
||||
c.Check(collection1, NotNil)
|
||||
|
||||
// Call with different DB creates different collection
|
||||
collection2 := s.factory.ChecksumCollection(s.db)
|
||||
c.Check(collection2, NotNil)
|
||||
c.Check(collection2, Not(Equals), collection1)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestFlush(c *C) {
|
||||
// Create all collections
|
||||
packages := s.factory.PackageCollection()
|
||||
remoteRepos := s.factory.RemoteRepoCollection()
|
||||
snapshots := s.factory.SnapshotCollection()
|
||||
localRepos := s.factory.LocalRepoCollection()
|
||||
publishedRepos := s.factory.PublishedRepoCollection()
|
||||
checksums := s.factory.ChecksumCollection(nil)
|
||||
|
||||
c.Check(packages, NotNil)
|
||||
c.Check(remoteRepos, NotNil)
|
||||
c.Check(snapshots, NotNil)
|
||||
c.Check(localRepos, NotNil)
|
||||
c.Check(publishedRepos, NotNil)
|
||||
c.Check(checksums, NotNil)
|
||||
|
||||
// Flush all collections
|
||||
s.factory.Flush()
|
||||
|
||||
// After flush, new calls should create new instances
|
||||
newPackages := s.factory.PackageCollection()
|
||||
newRemoteRepos := s.factory.RemoteRepoCollection()
|
||||
newSnapshots := s.factory.SnapshotCollection()
|
||||
newLocalRepos := s.factory.LocalRepoCollection()
|
||||
newPublishedRepos := s.factory.PublishedRepoCollection()
|
||||
newChecksums := s.factory.ChecksumCollection(nil)
|
||||
|
||||
c.Check(newPackages, Not(Equals), packages)
|
||||
c.Check(newRemoteRepos, Not(Equals), remoteRepos)
|
||||
c.Check(newSnapshots, Not(Equals), snapshots)
|
||||
c.Check(newLocalRepos, Not(Equals), localRepos)
|
||||
c.Check(newPublishedRepos, Not(Equals), publishedRepos)
|
||||
c.Check(newChecksums, Not(Equals), checksums)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestConcurrentAccess(c *C) {
|
||||
// Test that concurrent access to collections works properly
|
||||
done := make(chan bool, 10)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
// Each goroutine should get the same instances
|
||||
packages := s.factory.PackageCollection()
|
||||
remoteRepos := s.factory.RemoteRepoCollection()
|
||||
snapshots := s.factory.SnapshotCollection()
|
||||
localRepos := s.factory.LocalRepoCollection()
|
||||
publishedRepos := s.factory.PublishedRepoCollection()
|
||||
checksums := s.factory.ChecksumCollection(nil)
|
||||
|
||||
c.Check(packages, NotNil)
|
||||
c.Check(remoteRepos, NotNil)
|
||||
c.Check(snapshots, NotNil)
|
||||
c.Check(localRepos, NotNil)
|
||||
c.Check(publishedRepos, NotNil)
|
||||
c.Check(checksums, NotNil)
|
||||
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all goroutines to complete
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
// Verify that all collections are still accessible
|
||||
packages := s.factory.PackageCollection()
|
||||
c.Check(packages, NotNil)
|
||||
}
|
||||
|
||||
func (s *CollectionsSuite) TestFlushAndRecreate(c *C) {
|
||||
// Create collections, use them, flush, then recreate
|
||||
originalPackages := s.factory.PackageCollection()
|
||||
c.Check(originalPackages, NotNil)
|
||||
|
||||
// Add a package to test that it exists
|
||||
pkg := NewPackageFromControlFile(packageStanza.Copy())
|
||||
err := originalPackages.Update(pkg)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Flush
|
||||
s.factory.Flush()
|
||||
|
||||
// Get new collection
|
||||
newPackages := s.factory.PackageCollection()
|
||||
c.Check(newPackages, NotNil)
|
||||
c.Check(newPackages, Not(Equals), originalPackages)
|
||||
|
||||
// The package should still exist in the database
|
||||
retrievedPkg, err := newPackages.ByKey(pkg.Key(""))
|
||||
c.Check(err, IsNil)
|
||||
c.Check(retrievedPkg.Name, Equals, pkg.Name)
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type ContentsIndexSuite struct {
|
||||
mockDB *MockStorage
|
||||
}
|
||||
|
||||
var _ = Suite(&ContentsIndexSuite{})
|
||||
|
||||
func (s *ContentsIndexSuite) SetUpTest(c *C) {
|
||||
s.mockDB = &MockStorage{
|
||||
data: make(map[string][]byte),
|
||||
prefixes: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestNewContentsIndex(c *C) {
|
||||
// Test ContentsIndex creation
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
|
||||
c.Check(index, NotNil)
|
||||
c.Check(index.db, Equals, s.mockDB)
|
||||
c.Check(len(index.prefix), Equals, 36) // UUID length
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexEmpty(c *C) {
|
||||
// Test Empty method
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
|
||||
// Should be empty initially
|
||||
c.Check(index.Empty(), Equals, true)
|
||||
|
||||
// Add some data
|
||||
s.mockDB.prefixes[string(index.prefix)] = true
|
||||
|
||||
// Should not be empty now
|
||||
c.Check(index.Empty(), Equals, false)
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexPush(c *C) {
|
||||
// Test Push method
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
writer := &MockWriter{storage: s.mockDB}
|
||||
|
||||
qualifiedName := []byte("package_1.0_amd64")
|
||||
contents := []string{
|
||||
"/usr/bin/program",
|
||||
"/usr/share/doc/package/README",
|
||||
"/etc/package.conf",
|
||||
}
|
||||
|
||||
err := index.Push(qualifiedName, contents, writer)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Verify data was written
|
||||
c.Check(len(s.mockDB.data), Equals, 3)
|
||||
|
||||
// Check that keys contain the expected format
|
||||
for path := range contents {
|
||||
expectedKey := string(index.prefix) + contents[path] + "\x00" + string(qualifiedName)
|
||||
_, exists := s.mockDB.data[expectedKey]
|
||||
c.Check(exists, Equals, true, Commentf("Missing key for path: %s", contents[path]))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexPushError(c *C) {
|
||||
// Test Push method with writer error
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
writer := &MockWriter{storage: s.mockDB, shouldError: true}
|
||||
|
||||
qualifiedName := []byte("package_1.0_amd64")
|
||||
contents := []string{"/usr/bin/program"}
|
||||
|
||||
err := index.Push(qualifiedName, contents, writer)
|
||||
c.Check(err, NotNil)
|
||||
c.Check(err.Error(), Equals, "mock writer error")
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexWriteTo(c *C) {
|
||||
// Test WriteTo method
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
writer := &MockWriter{storage: s.mockDB}
|
||||
|
||||
// Add some packages
|
||||
err := index.Push([]byte("package1_1.0_amd64"), []string{"/usr/bin/prog1", "/usr/share/file1"}, writer)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
err = index.Push([]byte("package2_2.0_amd64"), []string{"/usr/bin/prog2", "/usr/share/file1"}, writer)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Set up processor to simulate database iteration
|
||||
s.mockDB.processor = func(prefix []byte, fn database.StorageProcessor) error {
|
||||
// Simulate database keys in sorted order
|
||||
keys := []string{
|
||||
string(prefix) + "/usr/bin/prog1\x00package1_1.0_amd64",
|
||||
string(prefix) + "/usr/bin/prog2\x00package2_2.0_amd64",
|
||||
string(prefix) + "/usr/share/file1\x00package1_1.0_amd64",
|
||||
string(prefix) + "/usr/share/file1\x00package2_2.0_amd64",
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
err := fn([]byte(key), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
n, err := index.WriteTo(&buf)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(n, Equals, int64(buf.Len()))
|
||||
|
||||
output := buf.String()
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
|
||||
// Should have header plus content lines
|
||||
c.Check(len(lines), Equals, 4)
|
||||
c.Check(lines[0], Equals, "FILE LOCATION")
|
||||
c.Check(lines[1], Equals, "/usr/bin/prog1 package1_1.0_amd64")
|
||||
c.Check(lines[2], Equals, "/usr/bin/prog2 package2_2.0_amd64")
|
||||
c.Check(lines[3], Equals, "/usr/share/file1 package1_1.0_amd64,package2_2.0_amd64")
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexWriteToEmpty(c *C) {
|
||||
// Test WriteTo with empty index
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
|
||||
s.mockDB.processor = func(prefix []byte, fn database.StorageProcessor) error {
|
||||
// No entries
|
||||
return nil
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
n, err := index.WriteTo(&buf)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(n, Equals, int64(buf.Len()))
|
||||
|
||||
output := buf.String()
|
||||
c.Check(output, Equals, "FILE LOCATION\n")
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexWriteToCorruptedEntry(c *C) {
|
||||
// Test WriteTo with corrupted database entry
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
|
||||
s.mockDB.processor = func(prefix []byte, fn database.StorageProcessor) error {
|
||||
// Corrupted key without null byte separator
|
||||
corruptedKey := string(prefix) + "/usr/bin/prog1package_name"
|
||||
return fn([]byte(corruptedKey), nil)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err := index.WriteTo(&buf)
|
||||
c.Check(err, NotNil)
|
||||
c.Check(err.Error(), Equals, "corrupted index entry")
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexPushMultiplePackages(c *C) {
|
||||
// Test pushing multiple packages
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
writer := &MockWriter{storage: s.mockDB}
|
||||
|
||||
packages := []struct {
|
||||
name string
|
||||
contents []string
|
||||
}{
|
||||
{"package1_1.0_amd64", []string{"/usr/bin/prog1", "/usr/share/doc1"}},
|
||||
{"package2_2.0_amd64", []string{"/usr/bin/prog2", "/usr/share/doc2"}},
|
||||
{"package3_3.0_amd64", []string{"/usr/bin/prog3"}},
|
||||
}
|
||||
|
||||
for _, pkg := range packages {
|
||||
err := index.Push([]byte(pkg.name), pkg.contents, writer)
|
||||
c.Check(err, IsNil, Commentf("Failed to push package: %s", pkg.name))
|
||||
}
|
||||
|
||||
// Verify all entries were written
|
||||
expectedEntries := 2 + 2 + 1 // Total files across all packages
|
||||
c.Check(len(s.mockDB.data), Equals, expectedEntries)
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexPushEmptyContents(c *C) {
|
||||
// Test pushing package with no contents
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
writer := &MockWriter{storage: s.mockDB}
|
||||
|
||||
err := index.Push([]byte("empty_package"), []string{}, writer)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Should not add any entries
|
||||
c.Check(len(s.mockDB.data), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexSpecialCharacters(c *C) {
|
||||
// Test with special characters in paths and package names
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
writer := &MockWriter{storage: s.mockDB}
|
||||
|
||||
qualifiedName := []byte("special-package_1.0+build1_amd64")
|
||||
contents := []string{
|
||||
"/usr/bin/prog-with-dashes",
|
||||
"/usr/share/file with spaces",
|
||||
"/etc/config.d/file.conf",
|
||||
}
|
||||
|
||||
err := index.Push(qualifiedName, contents, writer)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(len(s.mockDB.data), Equals, 3)
|
||||
}
|
||||
|
||||
func (s *ContentsIndexSuite) TestContentsIndexBinaryData(c *C) {
|
||||
// Test with binary data in paths (edge case)
|
||||
index := NewContentsIndex(s.mockDB)
|
||||
writer := &MockWriter{storage: s.mockDB}
|
||||
|
||||
// Path with binary data
|
||||
binaryPath := "/usr/bin/prog\x00\xFF\xFE"
|
||||
qualifiedName := []byte("binary_package_1.0_amd64")
|
||||
|
||||
err := index.Push(qualifiedName, []string{binaryPath}, writer)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(len(s.mockDB.data), Equals, 1)
|
||||
}
|
||||
|
||||
// Mock implementations for testing
|
||||
type MockStorage struct {
|
||||
data map[string][]byte
|
||||
prefixes map[string]bool
|
||||
processor func([]byte, database.StorageProcessor) error
|
||||
}
|
||||
|
||||
func (m *MockStorage) Get(key []byte) ([]byte, error) {
|
||||
if value, exists := m.data[string(key)]; exists {
|
||||
return value, nil
|
||||
}
|
||||
return nil, database.ErrNotFound
|
||||
}
|
||||
|
||||
func (m *MockStorage) Put(key, value []byte) error {
|
||||
m.data[string(key)] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) Delete(key []byte) error {
|
||||
delete(m.data, string(key))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) HasPrefix(prefix []byte) bool {
|
||||
if exists, ok := m.prefixes[string(prefix)]; ok {
|
||||
return exists
|
||||
}
|
||||
|
||||
// Check if any key has this prefix
|
||||
for key := range m.data {
|
||||
if strings.HasPrefix(key, string(prefix)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *MockStorage) ProcessByPrefix(prefix []byte, fn database.StorageProcessor) error {
|
||||
if m.processor != nil {
|
||||
return m.processor(prefix, fn)
|
||||
}
|
||||
|
||||
// Default implementation - process matching keys
|
||||
for key, value := range m.data {
|
||||
if strings.HasPrefix(key, string(prefix)) {
|
||||
err := fn([]byte(key), value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) KeysByPrefix(prefix []byte) [][]byte {
|
||||
var keys [][]byte
|
||||
for key := range m.data {
|
||||
if strings.HasPrefix(key, string(prefix)) {
|
||||
keys = append(keys, []byte(key))
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (m *MockStorage) FetchByPrefix(prefix []byte) [][]byte {
|
||||
var values [][]byte
|
||||
for key, value := range m.data {
|
||||
if strings.HasPrefix(key, string(prefix)) {
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func (m *MockStorage) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) CompactDB() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) Drop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) Open() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) CreateBatch() database.Batch {
|
||||
return &MockBatch{storage: m}
|
||||
}
|
||||
|
||||
func (m *MockStorage) OpenTransaction() (database.Transaction, error) {
|
||||
return &MockTransaction{storage: m}, nil
|
||||
}
|
||||
|
||||
func (m *MockStorage) CreateTemporary() (database.Storage, error) {
|
||||
return &MockStorage{
|
||||
data: make(map[string][]byte),
|
||||
prefixes: make(map[string]bool),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MockBatch struct {
|
||||
storage *MockStorage
|
||||
}
|
||||
|
||||
func (m *MockBatch) Put(key, value []byte) error {
|
||||
return m.storage.Put(key, value)
|
||||
}
|
||||
|
||||
func (m *MockBatch) Delete(key []byte) error {
|
||||
return m.storage.Delete(key)
|
||||
}
|
||||
|
||||
func (m *MockBatch) Write() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type MockTransaction struct {
|
||||
storage *MockStorage
|
||||
}
|
||||
|
||||
func (m *MockTransaction) Get(key []byte) ([]byte, error) {
|
||||
return m.storage.Get(key)
|
||||
}
|
||||
|
||||
func (m *MockTransaction) Put(key, value []byte) error {
|
||||
return m.storage.Put(key, value)
|
||||
}
|
||||
|
||||
func (m *MockTransaction) Delete(key []byte) error {
|
||||
return m.storage.Delete(key)
|
||||
}
|
||||
|
||||
func (m *MockTransaction) Commit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockTransaction) Discard() {
|
||||
}
|
||||
|
||||
type MockWriter struct {
|
||||
storage *MockStorage
|
||||
shouldError bool
|
||||
}
|
||||
|
||||
func (m *MockWriter) Put(key, value []byte) error {
|
||||
if m.shouldError {
|
||||
return &MockError{message: "mock writer error"}
|
||||
}
|
||||
return m.storage.Put(key, value)
|
||||
}
|
||||
|
||||
func (m *MockWriter) Delete(key []byte) error {
|
||||
if m.shouldError {
|
||||
return &MockError{message: "mock writer error"}
|
||||
}
|
||||
return m.storage.Delete(key)
|
||||
}
|
||||
|
||||
type MockError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *MockError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type GraphSuite struct {
|
||||
collectionFactory *CollectionFactory
|
||||
}
|
||||
|
||||
var _ = Suite(&GraphSuite{})
|
||||
|
||||
func (s *GraphSuite) SetUpTest(c *C) {
|
||||
db, _ := goleveldb.NewOpenDB(c.MkDir())
|
||||
s.collectionFactory = NewCollectionFactory(db)
|
||||
}
|
||||
|
||||
func (s *GraphSuite) TearDownTest(c *C) {
|
||||
// Collections are closed automatically when the test ends
|
||||
}
|
||||
|
||||
func (s *GraphSuite) TestBuildGraphBasic(c *C) {
|
||||
// Test BuildGraph with default (horizontal) layout
|
||||
graph, err := BuildGraph(s.collectionFactory, "horizontal")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(graph, NotNil)
|
||||
}
|
||||
|
||||
func (s *GraphSuite) TestBuildGraphVertical(c *C) {
|
||||
// Test BuildGraph with vertical layout
|
||||
graph, err := BuildGraph(s.collectionFactory, "vertical")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(graph, NotNil)
|
||||
}
|
||||
|
||||
func (s *GraphSuite) TestBuildGraphUnknownLayout(c *C) {
|
||||
// Test BuildGraph with unknown layout (should default to horizontal)
|
||||
graph, err := BuildGraph(s.collectionFactory, "unknown")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(graph, NotNil)
|
||||
}
|
||||
@@ -0,0 +1,877 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/aptly-dev/aptly/pgp"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type ImportSuite struct {
|
||||
tempDir string
|
||||
}
|
||||
|
||||
var _ = Suite(&ImportSuite{})
|
||||
|
||||
func (s *ImportSuite) SetUpTest(c *C) {
|
||||
s.tempDir = c.MkDir()
|
||||
}
|
||||
|
||||
type MockResultReporter struct {
|
||||
warnings []string
|
||||
added []string
|
||||
removed []string
|
||||
}
|
||||
|
||||
func (m *MockResultReporter) Warning(msg string, a ...interface{}) {
|
||||
m.warnings = append(m.warnings, fmt.Sprintf(msg, a...))
|
||||
}
|
||||
|
||||
func (m *MockResultReporter) Added(msg string, a ...interface{}) {
|
||||
m.added = append(m.added, fmt.Sprintf(msg, a...))
|
||||
}
|
||||
|
||||
func (m *MockResultReporter) Removed(msg string, a ...interface{}) {
|
||||
m.removed = append(m.removed, fmt.Sprintf(msg, a...))
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesEmpty(c *C) {
|
||||
// Test with empty locations list
|
||||
reporter := &MockResultReporter{}
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 0)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesNonExistentLocation(c *C) {
|
||||
// Test with non-existent location
|
||||
reporter := &MockResultReporter{}
|
||||
nonExistentPath := filepath.Join(s.tempDir, "nonexistent")
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{nonExistentPath}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 0)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1)
|
||||
c.Check(failedFiles[0], Equals, nonExistentPath)
|
||||
c.Check(len(reporter.warnings), Equals, 1)
|
||||
c.Check(strings.Contains(reporter.warnings[0], "Unable to process"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesSingleDebFile(c *C) {
|
||||
// Test with single .deb file
|
||||
reporter := &MockResultReporter{}
|
||||
debFile := filepath.Join(s.tempDir, "package.deb")
|
||||
|
||||
// Create dummy .deb file
|
||||
err := ioutil.WriteFile(debFile, []byte("dummy deb content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{debFile}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 1)
|
||||
c.Check(packageFiles[0], Equals, debFile)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesSingleUdebFile(c *C) {
|
||||
// Test with single .udeb file
|
||||
reporter := &MockResultReporter{}
|
||||
udebFile := filepath.Join(s.tempDir, "package.udeb")
|
||||
|
||||
err := ioutil.WriteFile(udebFile, []byte("dummy udeb content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{udebFile}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 1)
|
||||
c.Check(packageFiles[0], Equals, udebFile)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesSingleDscFile(c *C) {
|
||||
// Test with single .dsc file
|
||||
reporter := &MockResultReporter{}
|
||||
dscFile := filepath.Join(s.tempDir, "package.dsc")
|
||||
|
||||
err := ioutil.WriteFile(dscFile, []byte("dummy dsc content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{dscFile}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 1)
|
||||
c.Check(packageFiles[0], Equals, dscFile)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesSingleDdebFile(c *C) {
|
||||
// Test with single .ddeb file
|
||||
reporter := &MockResultReporter{}
|
||||
ddebFile := filepath.Join(s.tempDir, "package.ddeb")
|
||||
|
||||
err := ioutil.WriteFile(ddebFile, []byte("dummy ddeb content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{ddebFile}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 1)
|
||||
c.Check(packageFiles[0], Equals, ddebFile)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesBuildInfoFile(c *C) {
|
||||
// Test with .buildinfo file
|
||||
reporter := &MockResultReporter{}
|
||||
buildinfoFile := filepath.Join(s.tempDir, "package.buildinfo")
|
||||
|
||||
err := ioutil.WriteFile(buildinfoFile, []byte("dummy buildinfo content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{buildinfoFile}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 0)
|
||||
c.Check(len(otherFiles), Equals, 1)
|
||||
c.Check(otherFiles[0], Equals, buildinfoFile)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesUnknownExtension(c *C) {
|
||||
// Test with unknown file extension
|
||||
reporter := &MockResultReporter{}
|
||||
unknownFile := filepath.Join(s.tempDir, "package.unknown")
|
||||
|
||||
err := ioutil.WriteFile(unknownFile, []byte("dummy content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{unknownFile}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 0)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1)
|
||||
c.Check(failedFiles[0], Equals, unknownFile)
|
||||
c.Check(len(reporter.warnings), Equals, 1)
|
||||
c.Check(strings.Contains(reporter.warnings[0], "Unknown file extension"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesDirectory(c *C) {
|
||||
// Test with directory containing various files
|
||||
reporter := &MockResultReporter{}
|
||||
subDir := filepath.Join(s.tempDir, "packages")
|
||||
err := os.MkdirAll(subDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Create various file types
|
||||
files := map[string]string{
|
||||
"package1.deb": "deb content",
|
||||
"package2.udeb": "udeb content",
|
||||
"source.dsc": "dsc content",
|
||||
"debug.ddeb": "ddeb content",
|
||||
"build.buildinfo": "buildinfo content",
|
||||
"readme.txt": "text content",
|
||||
"subdir/nested.deb": "nested deb",
|
||||
}
|
||||
|
||||
// Create nested subdirectory
|
||||
nestedDir := filepath.Join(subDir, "subdir")
|
||||
err = os.MkdirAll(nestedDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
for filename, content := range files {
|
||||
fullPath := filepath.Join(subDir, filename)
|
||||
err := os.MkdirAll(filepath.Dir(fullPath), 0755)
|
||||
c.Assert(err, IsNil)
|
||||
err = ioutil.WriteFile(fullPath, []byte(content), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{subDir}, reporter)
|
||||
|
||||
// Should find package files (sorted)
|
||||
expectedPackageFiles := []string{
|
||||
filepath.Join(subDir, "debug.ddeb"),
|
||||
filepath.Join(subDir, "package1.deb"),
|
||||
filepath.Join(subDir, "package2.udeb"),
|
||||
filepath.Join(subDir, "source.dsc"),
|
||||
filepath.Join(subDir, "subdir", "nested.deb"),
|
||||
}
|
||||
sort.Strings(expectedPackageFiles)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 5)
|
||||
c.Check(packageFiles, DeepEquals, expectedPackageFiles)
|
||||
|
||||
// Should find other files
|
||||
c.Check(len(otherFiles), Equals, 1)
|
||||
c.Check(otherFiles[0], Equals, filepath.Join(subDir, "build.buildinfo"))
|
||||
|
||||
// No failed files
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesMixedLocations(c *C) {
|
||||
// Test with mix of files and directories
|
||||
reporter := &MockResultReporter{}
|
||||
|
||||
// Create individual file
|
||||
debFile := filepath.Join(s.tempDir, "single.deb")
|
||||
err := ioutil.WriteFile(debFile, []byte("single deb"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Create directory with files
|
||||
subDir := filepath.Join(s.tempDir, "multi")
|
||||
err = os.MkdirAll(subDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
dscFile := filepath.Join(subDir, "source.dsc")
|
||||
err = ioutil.WriteFile(dscFile, []byte("dsc content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{debFile, subDir}, reporter)
|
||||
|
||||
expectedFiles := []string{debFile, dscFile}
|
||||
sort.Strings(expectedFiles)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 2)
|
||||
c.Check(packageFiles, DeepEquals, expectedFiles)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesConcurrency(c *C) {
|
||||
// Test concurrent access during directory walking
|
||||
reporter := &MockResultReporter{}
|
||||
subDir := filepath.Join(s.tempDir, "concurrent")
|
||||
err := os.MkdirAll(subDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Create many files to test concurrent access
|
||||
for i := 0; i < 100; i++ {
|
||||
filename := filepath.Join(subDir, fmt.Sprintf("package%d.deb", i))
|
||||
err := ioutil.WriteFile(filename, []byte(fmt.Sprintf("content %d", i)), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
if i%10 == 0 {
|
||||
buildinfoFile := filepath.Join(subDir, fmt.Sprintf("build%d.buildinfo", i))
|
||||
err = ioutil.WriteFile(buildinfoFile, []byte(fmt.Sprintf("buildinfo %d", i)), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{subDir}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 100)
|
||||
c.Check(len(otherFiles), Equals, 10) // Every 10th file is buildinfo
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
|
||||
// Check that files are sorted
|
||||
c.Check(sort.StringsAreSorted(packageFiles), Equals, true)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesPermissionDenied(c *C) {
|
||||
// Test handling of permission denied errors
|
||||
reporter := &MockResultReporter{}
|
||||
|
||||
// Create directory and remove read permission (if running as non-root)
|
||||
subDir := filepath.Join(s.tempDir, "noperm")
|
||||
err := os.MkdirAll(subDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Create a file inside
|
||||
testFile := filepath.Join(subDir, "test.deb")
|
||||
err = ioutil.WriteFile(testFile, []byte("test"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Remove read permission from directory
|
||||
err = os.Chmod(subDir, 0000)
|
||||
if err != nil {
|
||||
c.Skip("Cannot remove permissions, likely running as root")
|
||||
}
|
||||
defer os.Chmod(subDir, 0755) // Restore for cleanup
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{subDir}, reporter)
|
||||
|
||||
// Should handle permission error gracefully
|
||||
c.Check(len(packageFiles), Equals, 0)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1)
|
||||
c.Check(failedFiles[0], Equals, subDir)
|
||||
c.Check(len(reporter.warnings), Equals, 1)
|
||||
c.Check(strings.Contains(reporter.warnings[0], "Unable to process"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesEmptyDirectory(c *C) {
|
||||
// Test with empty directory
|
||||
reporter := &MockResultReporter{}
|
||||
emptyDir := filepath.Join(s.tempDir, "empty")
|
||||
err := os.MkdirAll(emptyDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{emptyDir}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 0)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesNestedDirectories(c *C) {
|
||||
// Test deeply nested directory structure
|
||||
reporter := &MockResultReporter{}
|
||||
|
||||
// Create nested structure: base/level1/level2/level3/
|
||||
deepDir := filepath.Join(s.tempDir, "base", "level1", "level2", "level3")
|
||||
err := os.MkdirAll(deepDir, 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Place files at different levels
|
||||
files := map[string]string{
|
||||
filepath.Join(s.tempDir, "base", "root.deb"): "root",
|
||||
filepath.Join(s.tempDir, "base", "level1", "level1.deb"): "level1",
|
||||
filepath.Join(s.tempDir, "base", "level1", "level2", "level2.deb"): "level2",
|
||||
filepath.Join(s.tempDir, "base", "level1", "level2", "level3", "deep.deb"): "deep",
|
||||
}
|
||||
|
||||
for path, content := range files {
|
||||
err := ioutil.WriteFile(path, []byte(content), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{filepath.Join(s.tempDir, "base")}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, 4)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
|
||||
// Verify all nested files were found
|
||||
for expectedPath := range files {
|
||||
found := false
|
||||
for _, foundPath := range packageFiles {
|
||||
if foundPath == expectedPath {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
c.Check(found, Equals, true, Commentf("File not found: %s", expectedPath))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesCaseInsensitive(c *C) {
|
||||
// Test case sensitivity of file extensions
|
||||
reporter := &MockResultReporter{}
|
||||
|
||||
// Create files with various case extensions
|
||||
files := []string{
|
||||
"package.deb",
|
||||
"package.DEB",
|
||||
"package.Deb",
|
||||
"source.dsc",
|
||||
"source.DSC",
|
||||
"package.udeb",
|
||||
"package.UDEB",
|
||||
"debug.ddeb",
|
||||
"debug.DDEB",
|
||||
}
|
||||
|
||||
for _, filename := range files {
|
||||
path := filepath.Join(s.tempDir, filename)
|
||||
err := ioutil.WriteFile(path, []byte("content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{s.tempDir}, reporter)
|
||||
|
||||
// Only lowercase extensions should be recognized
|
||||
c.Check(len(packageFiles), Equals, 4) // .deb, .dsc, .udeb, .ddeb (lowercase only)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
// Uppercase extensions are silently ignored by the file walker, not reported as failed
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesSymlinks(c *C) {
|
||||
// Test handling of symbolic links
|
||||
reporter := &MockResultReporter{}
|
||||
|
||||
// Create a real file
|
||||
realFile := filepath.Join(s.tempDir, "real.deb")
|
||||
err := ioutil.WriteFile(realFile, []byte("real content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Create a symlink to it
|
||||
linkFile := filepath.Join(s.tempDir, "link.deb")
|
||||
err = os.Symlink(realFile, linkFile)
|
||||
if err != nil {
|
||||
c.Skip("Cannot create symlinks on this filesystem")
|
||||
}
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{s.tempDir}, reporter)
|
||||
|
||||
// Both real file and symlink should be found
|
||||
c.Check(len(packageFiles), Equals, 2)
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestCollectPackageFilesSpecialCharacters(c *C) {
|
||||
// Test files with special characters in names
|
||||
reporter := &MockResultReporter{}
|
||||
|
||||
// Create files with various special characters
|
||||
specialFiles := []string{
|
||||
"package with spaces.deb",
|
||||
"package-with-dashes.deb",
|
||||
"package_with_underscores.deb",
|
||||
"package.1.0-1.deb",
|
||||
"package+plus.deb",
|
||||
"package@at.deb",
|
||||
}
|
||||
|
||||
for _, filename := range specialFiles {
|
||||
path := filepath.Join(s.tempDir, filename)
|
||||
err := ioutil.WriteFile(path, []byte("content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{s.tempDir}, reporter)
|
||||
|
||||
c.Check(len(packageFiles), Equals, len(specialFiles))
|
||||
c.Check(len(otherFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
}
|
||||
|
||||
// Mock implementations for ImportPackageFiles testing
|
||||
|
||||
type MockPackagePool struct {
|
||||
importFunc func(string, string, *utils.ChecksumInfo, bool, aptly.ChecksumStorage) (string, error)
|
||||
verifyFunc func(string, string, *utils.ChecksumInfo, aptly.ChecksumStorage) (string, bool, error)
|
||||
}
|
||||
|
||||
func (m *MockPackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) {
|
||||
if m.importFunc != nil {
|
||||
return m.importFunc(srcPath, basename, checksums, move, storage)
|
||||
}
|
||||
return "pool/" + basename, nil
|
||||
}
|
||||
|
||||
func (m *MockPackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, storage aptly.ChecksumStorage) (string, bool, error) {
|
||||
if m.verifyFunc != nil {
|
||||
return m.verifyFunc(poolPath, basename, checksums, storage)
|
||||
}
|
||||
return poolPath, true, nil
|
||||
}
|
||||
|
||||
func (m *MockPackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) {
|
||||
return "legacy/" + filename, nil
|
||||
}
|
||||
|
||||
func (m *MockPackagePool) Size(path string) (int64, error) {
|
||||
return 1024, nil
|
||||
}
|
||||
|
||||
func (m *MockPackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockPackagePool) FilepathList(progress aptly.Progress) ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (m *MockPackagePool) Remove(path string) (int64, error) {
|
||||
return 1024, nil
|
||||
}
|
||||
|
||||
type MockVerifier struct {
|
||||
verifyFunc func(string, string, string) (bool, error)
|
||||
}
|
||||
|
||||
func (m *MockVerifier) ExtractClearsign(signedMessage string) (string, error) {
|
||||
return signedMessage, nil
|
||||
}
|
||||
|
||||
func (m *MockVerifier) VerifyClearsign(clearsignInput string, keyringName string, showKeyInfo bool) (string, string, error) {
|
||||
if m.verifyFunc != nil {
|
||||
if valid, err := m.verifyFunc(clearsignInput, keyringName, ""); err != nil {
|
||||
return "", "", err
|
||||
} else if !valid {
|
||||
return "", "", fmt.Errorf("verification failed")
|
||||
}
|
||||
}
|
||||
return clearsignInput, "", nil
|
||||
}
|
||||
|
||||
// Add missing methods to implement pgp.Verifier interface
|
||||
func (m *MockVerifier) InitKeyring(verbose bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockVerifier) AddKeyring(keyring string) {
|
||||
// Mock implementation
|
||||
}
|
||||
|
||||
func (m *MockVerifier) VerifyDetachedSignature(signature, cleartext io.Reader, showKeyTip bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockVerifier) IsClearSigned(clearsigned io.Reader) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *MockVerifier) VerifyClearsigned(clearsigned io.Reader, showKeyTip bool) (*pgp.KeyInfo, error) {
|
||||
return &pgp.KeyInfo{}, nil
|
||||
}
|
||||
|
||||
func (m *MockVerifier) ExtractClearsigned(clearsigned io.Reader) (*os.File, error) {
|
||||
// Create a temporary file for mock
|
||||
tmpFile, err := ioutil.TempFile("", "mock_extract")
|
||||
return tmpFile, err
|
||||
}
|
||||
|
||||
|
||||
type MockPackageCollection struct {
|
||||
updateFunc func(*Package) error
|
||||
packages map[string]*Package
|
||||
}
|
||||
|
||||
func (m *MockPackageCollection) Update(p *Package) error {
|
||||
if m.updateFunc != nil {
|
||||
return m.updateFunc(p)
|
||||
}
|
||||
if m.packages == nil {
|
||||
m.packages = make(map[string]*Package)
|
||||
}
|
||||
m.packages[string(p.Key(""))] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPackageCollection) ByKey(key []byte) (*Package, error) {
|
||||
if m.packages == nil {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
if pkg, exists := m.packages[string(key)]; exists {
|
||||
return pkg, nil
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
type MockChecksumStorage struct {
|
||||
getFunc func(string) (*utils.ChecksumInfo, error)
|
||||
updateFunc func(string, *utils.ChecksumInfo) error
|
||||
}
|
||||
|
||||
func (m *MockChecksumStorage) Get(path string) (*utils.ChecksumInfo, error) {
|
||||
if m.getFunc != nil {
|
||||
return m.getFunc(path)
|
||||
}
|
||||
return &utils.ChecksumInfo{}, nil
|
||||
}
|
||||
|
||||
func (m *MockChecksumStorage) Update(path string, c *utils.ChecksumInfo) error {
|
||||
if m.updateFunc != nil {
|
||||
return m.updateFunc(path, c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesEmptyList(c *C) {
|
||||
// Test ImportPackageFiles with empty file list
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{}, false, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 0)
|
||||
c.Check(len(reporter.warnings), Equals, 0)
|
||||
c.Check(len(reporter.added), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesNonExistentFile(c *C) {
|
||||
// Test ImportPackageFiles with non-existent file
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
nonExistentFile := filepath.Join(s.tempDir, "nonexistent.deb")
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{nonExistentFile}, false, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1)
|
||||
c.Check(failedFiles[0], Equals, nonExistentFile)
|
||||
c.Check(len(reporter.warnings), Equals, 1)
|
||||
c.Check(strings.Contains(reporter.warnings[0], "Unable to read file"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesInvalidPackageFile(c *C) {
|
||||
// Test ImportPackageFiles with invalid package file
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
// Create invalid .deb file
|
||||
invalidDeb := filepath.Join(s.tempDir, "invalid.deb")
|
||||
err := ioutil.WriteFile(invalidDeb, []byte("not a valid deb file"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{invalidDeb}, false, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1)
|
||||
c.Check(failedFiles[0], Equals, invalidDeb)
|
||||
c.Check(len(reporter.warnings), Equals, 1)
|
||||
c.Check(strings.Contains(reporter.warnings[0], "Unable to read file"), Equals, true)
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesPoolImportError(c *C) {
|
||||
// Test ImportPackageFiles with pool import error
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
|
||||
// Mock pool that fails to import
|
||||
pool := &MockPackagePool{
|
||||
importFunc: func(string, string, *utils.ChecksumInfo, bool, aptly.ChecksumStorage) (string, error) {
|
||||
return "", fmt.Errorf("pool import error")
|
||||
},
|
||||
}
|
||||
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
// Create a simple .deb file
|
||||
debFile := filepath.Join(s.tempDir, "test.deb")
|
||||
err := ioutil.WriteFile(debFile, []byte("simple deb"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{debFile}, false, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1)
|
||||
c.Check(failedFiles[0], Equals, debFile)
|
||||
c.Check(len(reporter.warnings), Equals, 1) // One warning for file processing issue
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesCollectionUpdateError(c *C) {
|
||||
// Test ImportPackageFiles with collection update error
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
|
||||
// Use real collection for testing
|
||||
collection := NewPackageCollection(nil)
|
||||
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
// Create a simple .deb file
|
||||
debFile := filepath.Join(s.tempDir, "test.deb")
|
||||
err := ioutil.WriteFile(debFile, []byte("simple deb"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{debFile}, false, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1)
|
||||
c.Check(len(reporter.warnings), Equals, 1) // One warning for file processing issue
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesForceReplace(c *C) {
|
||||
// Test ImportPackageFiles with force replace option
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
// Test that forceReplace calls PrepareIndex on the list
|
||||
debFile := filepath.Join(s.tempDir, "test.deb")
|
||||
err := ioutil.WriteFile(debFile, []byte("simple deb"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// With forceReplace = true
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{debFile}, true, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0) // No files should be processed due to invalid file
|
||||
// Even though the file is invalid, the function should handle forceReplace logic
|
||||
c.Check(len(failedFiles), Equals, 1) // Will fail due to invalid deb file
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesErrorHandling(c *C) {
|
||||
// Test various error conditions in ImportPackageFiles
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
// Test with multiple files, some valid some invalid
|
||||
validDeb := filepath.Join(s.tempDir, "valid.deb")
|
||||
invalidDeb := filepath.Join(s.tempDir, "invalid.deb")
|
||||
nonExistent := filepath.Join(s.tempDir, "nonexistent.deb")
|
||||
|
||||
err := ioutil.WriteFile(validDeb, []byte("valid deb content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = ioutil.WriteFile(invalidDeb, []byte("invalid content"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
files := []string{validDeb, invalidDeb, nonExistent}
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, files, false, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0) // No files should be processed successfully
|
||||
c.Check(len(failedFiles), Equals, 3) // All files should fail
|
||||
c.Check(len(reporter.warnings), Equals, 3) // Should have warnings for all failures
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesRestrictionFilter(c *C) {
|
||||
// Test ImportPackageFiles with package restriction filter
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
// Create mock restriction that rejects all packages
|
||||
restriction := &MockPackageQuery{
|
||||
matchesFunc: func(*Package) bool {
|
||||
return false // Reject all packages
|
||||
},
|
||||
}
|
||||
|
||||
debFile := filepath.Join(s.tempDir, "test.deb")
|
||||
err := ioutil.WriteFile(debFile, []byte("test deb"), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{debFile}, false, verifier, pool, collection, reporter, restriction, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(len(processedFiles), Equals, 0)
|
||||
c.Check(len(failedFiles), Equals, 1) // Should fail due to restriction + invalid file
|
||||
c.Check(len(reporter.warnings) >= 1, Equals, true)
|
||||
}
|
||||
|
||||
type MockPackageQuery struct {
|
||||
matchesFunc func(*Package) bool
|
||||
}
|
||||
|
||||
func (m *MockPackageQuery) Matches(p PackageLike) bool {
|
||||
if m.matchesFunc != nil {
|
||||
if pkg, ok := p.(*Package); ok {
|
||||
return m.matchesFunc(pkg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *MockPackageQuery) Fast(_ PackageCatalog) bool {
|
||||
return false // Mock implementation returns false for simplicity
|
||||
}
|
||||
|
||||
func (m *MockPackageQuery) Query(list PackageCatalog) *PackageList {
|
||||
return list.Scan(m) // Default implementation
|
||||
}
|
||||
|
||||
func (m *MockPackageQuery) String() string {
|
||||
return "MockPackageQuery"
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesFileTypes(c *C) {
|
||||
// Test ImportPackageFiles with different file types
|
||||
list := NewPackageList()
|
||||
reporter := &MockResultReporter{}
|
||||
pool := &MockPackagePool{}
|
||||
collection := NewPackageCollection(nil)
|
||||
verifier := &MockVerifier{}
|
||||
checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage {
|
||||
return &MockChecksumStorage{}
|
||||
}
|
||||
|
||||
// Create files of different types
|
||||
files := map[string]string{
|
||||
"package.deb": "deb content",
|
||||
"package.udeb": "udeb content",
|
||||
"source.dsc": "dsc content",
|
||||
"debug.ddeb": "ddeb content",
|
||||
}
|
||||
|
||||
var fileList []string
|
||||
for filename, content := range files {
|
||||
path := filepath.Join(s.tempDir, filename)
|
||||
err := ioutil.WriteFile(path, []byte(content), 0644)
|
||||
c.Assert(err, IsNil)
|
||||
fileList = append(fileList, path)
|
||||
}
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, fileList, false, verifier, pool, collection, reporter, nil, checksumProvider)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
// All files should fail due to invalid format, but function should handle different types
|
||||
c.Check(len(failedFiles), Equals, len(fileList))
|
||||
c.Check(len(processedFiles), Equals, 0)
|
||||
}
|
||||
@@ -0,0 +1,737 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type IndexFilesSuite struct {
|
||||
tempDir string
|
||||
publishedStorage *MockPublishedStorage
|
||||
indexFiles *indexFiles
|
||||
}
|
||||
|
||||
var _ = Suite(&IndexFilesSuite{})
|
||||
|
||||
func (s *IndexFilesSuite) SetUpTest(c *C) {
|
||||
s.tempDir = c.MkDir()
|
||||
s.publishedStorage = &MockPublishedStorage{
|
||||
files: make(map[string]string),
|
||||
dirs: make(map[string]bool),
|
||||
links: make(map[string]string),
|
||||
symlinks: make(map[string]string),
|
||||
}
|
||||
s.indexFiles = newIndexFiles(s.publishedStorage, "dists/test", s.tempDir, "", false, false)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestNewIndexFiles(c *C) {
|
||||
// Test creation of indexFiles struct
|
||||
basePath := "dists/testing"
|
||||
tempDir := "/tmp/test"
|
||||
suffix := ".new"
|
||||
acquireByHash := true
|
||||
skipBz2 := true
|
||||
|
||||
files := newIndexFiles(s.publishedStorage, basePath, tempDir, suffix, acquireByHash, skipBz2)
|
||||
|
||||
c.Check(files.publishedStorage, Equals, s.publishedStorage)
|
||||
c.Check(files.basePath, Equals, basePath)
|
||||
c.Check(files.tempDir, Equals, tempDir)
|
||||
c.Check(files.suffix, Equals, suffix)
|
||||
c.Check(files.acquireByHash, Equals, acquireByHash)
|
||||
c.Check(files.skipBz2, Equals, skipBz2)
|
||||
c.Check(files.renameMap, NotNil)
|
||||
c.Check(files.generatedFiles, NotNil)
|
||||
c.Check(files.indexes, NotNil)
|
||||
c.Check(len(files.renameMap), Equals, 0)
|
||||
c.Check(len(files.generatedFiles), Equals, 0)
|
||||
c.Check(len(files.indexes), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileBufWriter(c *C) {
|
||||
// Test indexFile BufWriter creation
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
}
|
||||
|
||||
// First call should create the writer
|
||||
writer, err := file.BufWriter()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(writer, NotNil)
|
||||
c.Check(file.w, Equals, writer)
|
||||
c.Check(file.tempFile, NotNil)
|
||||
c.Check(file.tempFilename, Matches, ".*main_binary-amd64_Packages")
|
||||
|
||||
// Second call should return the same writer
|
||||
writer2, err := file.BufWriter()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(writer2, Equals, writer)
|
||||
|
||||
// Clean up
|
||||
file.tempFile.Close()
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileBufWriterError(c *C) {
|
||||
// Test BufWriter creation with invalid temp directory
|
||||
invalidFiles := newIndexFiles(s.publishedStorage, "dists/test", "/invalid/path", "", false, false)
|
||||
file := &indexFile{
|
||||
parent: invalidFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
}
|
||||
|
||||
_, err := file.BufWriter()
|
||||
c.Check(err, NotNil)
|
||||
c.Check(err.Error(), Matches, ".*unable to create temporary index file.*")
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileFinalize(c *C) {
|
||||
// Test basic finalization of index file
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
compressable: false,
|
||||
detachedSign: false,
|
||||
clearSign: false,
|
||||
acquireByHash: false,
|
||||
}
|
||||
|
||||
// Write some content to the file
|
||||
writer, err := file.BufWriter()
|
||||
c.Check(err, IsNil)
|
||||
writer.WriteString("Package: test-package\nVersion: 1.0\n\n")
|
||||
|
||||
err = file.Finalize(nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that file was published
|
||||
c.Check(s.publishedStorage.files["dists/test/main/binary-amd64/Packages"], NotNil)
|
||||
c.Check(s.publishedStorage.dirs["dists/test/main/binary-amd64"], Equals, true)
|
||||
|
||||
// Check that checksums were generated
|
||||
c.Check(s.indexFiles.generatedFiles["main/binary-amd64/Packages"], NotNil)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileFinalizeCompressable(c *C) {
|
||||
// Test finalization with compression
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
compressable: true,
|
||||
detachedSign: false,
|
||||
clearSign: false,
|
||||
acquireByHash: false,
|
||||
onlyGzip: false,
|
||||
}
|
||||
|
||||
// Write content and finalize
|
||||
writer, err := file.BufWriter()
|
||||
c.Check(err, IsNil)
|
||||
writer.WriteString("Package: test-package\nVersion: 1.0\n\n")
|
||||
|
||||
err = file.Finalize(nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that compressed files were published
|
||||
c.Check(s.publishedStorage.files["dists/test/main/binary-amd64/Packages"], NotNil)
|
||||
c.Check(s.publishedStorage.files["dists/test/main/binary-amd64/Packages.gz"], NotNil)
|
||||
|
||||
// With skipBz2 = false, should also have .bz2
|
||||
if !s.indexFiles.skipBz2 {
|
||||
c.Check(s.publishedStorage.files["dists/test/main/binary-amd64/Packages.bz2"], NotNil)
|
||||
}
|
||||
|
||||
// Check checksums for all variants
|
||||
c.Check(s.indexFiles.generatedFiles["main/binary-amd64/Packages"], NotNil)
|
||||
c.Check(s.indexFiles.generatedFiles["main/binary-amd64/Packages.gz"], NotNil)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileFinalizeOnlyGzip(c *C) {
|
||||
// Test finalization with only gzip compression
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/Contents-amd64",
|
||||
compressable: true,
|
||||
onlyGzip: true,
|
||||
detachedSign: false,
|
||||
clearSign: false,
|
||||
acquireByHash: false,
|
||||
}
|
||||
|
||||
writer, err := file.BufWriter()
|
||||
c.Check(err, IsNil)
|
||||
writer.WriteString("some content data\n")
|
||||
|
||||
err = file.Finalize(nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Should only have .gz file, not .bz2
|
||||
c.Check(s.publishedStorage.files["dists/test/main/Contents-amd64.gz"], NotNil)
|
||||
_, hasBz2 := s.publishedStorage.files["dists/test/main/Contents-amd64.bz2"]
|
||||
c.Check(hasBz2, Equals, false)
|
||||
|
||||
// Checksums should include both uncompressed and compressed
|
||||
c.Check(s.indexFiles.generatedFiles["main/Contents-amd64"], NotNil)
|
||||
c.Check(s.indexFiles.generatedFiles["main/Contents-amd64.gz"], NotNil)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileFinalizeDiscardable(c *C) {
|
||||
// Test finalization of discardable file (should create empty file)
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/debian-installer/binary-amd64/Release",
|
||||
discardable: true,
|
||||
compressable: false,
|
||||
detachedSign: false,
|
||||
clearSign: false,
|
||||
acquireByHash: false,
|
||||
}
|
||||
|
||||
// Don't write any content, just finalize
|
||||
err := file.Finalize(nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Should still create the file
|
||||
c.Check(s.publishedStorage.files["dists/test/main/debian-installer/binary-amd64/Release"], NotNil)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileFinalizeSigning(c *C) {
|
||||
// Test finalization with signing
|
||||
mockSigner := &MockSigner{}
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "Release",
|
||||
compressable: false,
|
||||
detachedSign: true,
|
||||
clearSign: true,
|
||||
acquireByHash: false,
|
||||
}
|
||||
|
||||
writer, err := file.BufWriter()
|
||||
c.Check(err, IsNil)
|
||||
writer.WriteString("Suite: test\nCodename: test\n")
|
||||
|
||||
err = file.Finalize(mockSigner)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that signed files were created
|
||||
c.Check(s.publishedStorage.files["dists/test/Release"], NotNil)
|
||||
c.Check(s.publishedStorage.files["dists/test/Release.gpg"], NotNil)
|
||||
c.Check(s.publishedStorage.files["dists/test/InRelease"], NotNil)
|
||||
|
||||
// Check that signer methods were called
|
||||
c.Check(mockSigner.DetachedSignCalled, Equals, true)
|
||||
c.Check(mockSigner.ClearSignCalled, Equals, true)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestIndexFileFinalizeWithSuffix(c *C) {
|
||||
// Test finalization with suffix (for atomic updates)
|
||||
s.indexFiles.suffix = ".new"
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
compressable: false,
|
||||
detachedSign: false,
|
||||
clearSign: false,
|
||||
acquireByHash: false,
|
||||
}
|
||||
|
||||
writer, err := file.BufWriter()
|
||||
c.Check(err, IsNil)
|
||||
writer.WriteString("Package: test\n")
|
||||
|
||||
err = file.Finalize(nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that file was published with suffix
|
||||
c.Check(s.publishedStorage.files["dists/test/main/binary-amd64/Packages.new"], NotNil)
|
||||
|
||||
// Check that rename mapping was created
|
||||
expectedTarget := "dists/test/main/binary-amd64/Packages"
|
||||
c.Check(s.indexFiles.renameMap["dists/test/main/binary-amd64/Packages.new"], Equals, expectedTarget)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestPackageIndex(c *C) {
|
||||
// Test PackageIndex creation for binary packages
|
||||
file := s.indexFiles.PackageIndex("main", "amd64", false, false, "")
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/binary-amd64/Packages")
|
||||
c.Check(file.compressable, Equals, true)
|
||||
c.Check(file.discardable, Equals, false)
|
||||
c.Check(file.detachedSign, Equals, false)
|
||||
|
||||
// Test that same call returns cached instance
|
||||
file2 := s.indexFiles.PackageIndex("main", "amd64", false, false, "")
|
||||
c.Check(file2, Equals, file)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestPackageIndexSource(c *C) {
|
||||
// Test PackageIndex creation for source packages
|
||||
file := s.indexFiles.PackageIndex("main", ArchitectureSource, false, false, "")
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/source/Sources")
|
||||
c.Check(file.compressable, Equals, true)
|
||||
c.Check(file.discardable, Equals, false)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestPackageIndexUdeb(c *C) {
|
||||
// Test PackageIndex creation for udeb packages
|
||||
file := s.indexFiles.PackageIndex("main", "amd64", true, false, "")
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/debian-installer/binary-amd64/Packages")
|
||||
c.Check(file.compressable, Equals, true)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestPackageIndexInstaller(c *C) {
|
||||
// Test PackageIndex creation for installer images
|
||||
file := s.indexFiles.PackageIndex("main", "amd64", false, true, "")
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/installer-amd64/current/images/SHA256SUMS")
|
||||
c.Check(file.compressable, Equals, false)
|
||||
c.Check(file.detachedSign, Equals, true)
|
||||
|
||||
// Test focal distribution special case
|
||||
fileFocal := s.indexFiles.PackageIndex("main", "amd64", false, true, aptly.DistributionFocal)
|
||||
c.Check(fileFocal, NotNil)
|
||||
c.Check(fileFocal.relativePath, Equals, "main/installer-amd64/current/legacy-images/SHA256SUMS")
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestReleaseIndex(c *C) {
|
||||
// Test ReleaseIndex creation for binary architecture
|
||||
file := s.indexFiles.ReleaseIndex("main", "amd64", false)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/binary-amd64/Release")
|
||||
c.Check(file.compressable, Equals, false)
|
||||
c.Check(file.discardable, Equals, false)
|
||||
|
||||
// Test that same call returns cached instance
|
||||
file2 := s.indexFiles.ReleaseIndex("main", "amd64", false)
|
||||
c.Check(file2, Equals, file)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestReleaseIndexSource(c *C) {
|
||||
// Test ReleaseIndex creation for source architecture
|
||||
file := s.indexFiles.ReleaseIndex("main", ArchitectureSource, false)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/source/Release")
|
||||
c.Check(file.compressable, Equals, false)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestReleaseIndexUdeb(c *C) {
|
||||
// Test ReleaseIndex creation for udeb (should be discardable)
|
||||
file := s.indexFiles.ReleaseIndex("main", "amd64", true)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/debian-installer/binary-amd64/Release")
|
||||
c.Check(file.discardable, Equals, true)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestContentsIndex(c *C) {
|
||||
// Test ContentsIndex creation for regular packages
|
||||
file := s.indexFiles.ContentsIndex("main", "amd64", false)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/Contents-amd64")
|
||||
c.Check(file.compressable, Equals, true)
|
||||
c.Check(file.onlyGzip, Equals, true)
|
||||
c.Check(file.discardable, Equals, true)
|
||||
|
||||
// Test that same call returns cached instance
|
||||
file2 := s.indexFiles.ContentsIndex("main", "amd64", false)
|
||||
c.Check(file2, Equals, file)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestContentsIndexUdeb(c *C) {
|
||||
// Test ContentsIndex creation for udeb packages
|
||||
file := s.indexFiles.ContentsIndex("main", "amd64", true)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/Contents-udeb-amd64")
|
||||
c.Check(file.compressable, Equals, true)
|
||||
c.Check(file.onlyGzip, Equals, true)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestContentsIndexSource(c *C) {
|
||||
// Test ContentsIndex for source architecture (should not have udeb)
|
||||
file := s.indexFiles.ContentsIndex("main", ArchitectureSource, true)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/Contents-source")
|
||||
// udeb flag should be ignored for source
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestLegacyContentsIndex(c *C) {
|
||||
// Test LegacyContentsIndex creation
|
||||
file := s.indexFiles.LegacyContentsIndex("amd64", false)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "Contents-amd64")
|
||||
c.Check(file.compressable, Equals, true)
|
||||
c.Check(file.onlyGzip, Equals, true)
|
||||
c.Check(file.discardable, Equals, true)
|
||||
|
||||
// Test that same call returns cached instance
|
||||
file2 := s.indexFiles.LegacyContentsIndex("amd64", false)
|
||||
c.Check(file2, Equals, file)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestLegacyContentsIndexUdeb(c *C) {
|
||||
// Test LegacyContentsIndex for udeb
|
||||
file := s.indexFiles.LegacyContentsIndex("amd64", true)
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "Contents-udeb-amd64")
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestSkelIndex(c *C) {
|
||||
// Test SkelIndex creation
|
||||
file := s.indexFiles.SkelIndex("main", "extra/file.txt")
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "main/extra/file.txt")
|
||||
c.Check(file.compressable, Equals, false)
|
||||
c.Check(file.discardable, Equals, false)
|
||||
|
||||
// Test that same call returns cached instance
|
||||
file2 := s.indexFiles.SkelIndex("main", "extra/file.txt")
|
||||
c.Check(file2, Equals, file)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestReleaseFile(c *C) {
|
||||
// Test ReleaseFile creation (should not be cached)
|
||||
file := s.indexFiles.ReleaseFile()
|
||||
c.Check(file, NotNil)
|
||||
c.Check(file.relativePath, Equals, "Release")
|
||||
c.Check(file.compressable, Equals, false)
|
||||
c.Check(file.detachedSign, Equals, true)
|
||||
c.Check(file.clearSign, Equals, true)
|
||||
|
||||
// Test that new call returns different instance (not cached)
|
||||
file2 := s.indexFiles.ReleaseFile()
|
||||
c.Check(file2, Not(Equals), file)
|
||||
c.Check(file2.relativePath, Equals, "Release")
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestFinalizeAll(c *C) {
|
||||
// Test finalizing all index files
|
||||
mockSigner := &MockSigner{}
|
||||
mockProgress := &MockProgress{}
|
||||
|
||||
// Create some index files
|
||||
file1 := s.indexFiles.PackageIndex("main", "amd64", false, false, "")
|
||||
file2 := s.indexFiles.ContentsIndex("main", "amd64", false)
|
||||
|
||||
// Write content to files
|
||||
writer1, _ := file1.BufWriter()
|
||||
writer1.WriteString("Package: test1\n")
|
||||
writer2, _ := file2.BufWriter()
|
||||
writer2.WriteString("test1 section/file")
|
||||
|
||||
err := s.indexFiles.FinalizeAll(mockProgress, mockSigner)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that files were published
|
||||
c.Check(len(s.publishedStorage.files) > 0, Equals, true)
|
||||
|
||||
// Check that progress was tracked
|
||||
c.Check(mockProgress.InitBarCalled, Equals, true)
|
||||
c.Check(mockProgress.ShutdownBarCalled, Equals, true)
|
||||
c.Check(mockProgress.AddBarCount >= 2, Equals, true)
|
||||
|
||||
// Check that indexes map is cleared
|
||||
c.Check(len(s.indexFiles.indexes), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestFinalizeAllNoProgress(c *C) {
|
||||
// Test finalizing without progress tracking
|
||||
file := s.indexFiles.PackageIndex("main", "amd64", false, false, "")
|
||||
writer, _ := file.BufWriter()
|
||||
writer.WriteString("Package: test\n")
|
||||
|
||||
err := s.indexFiles.FinalizeAll(nil, nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(len(s.publishedStorage.files) > 0, Equals, true)
|
||||
c.Check(len(s.indexFiles.indexes), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestRenameFiles(c *C) {
|
||||
// Test file renaming functionality
|
||||
s.indexFiles.renameMap["old/path"] = "new/path"
|
||||
s.indexFiles.renameMap["another/old"] = "another/new"
|
||||
|
||||
err := s.indexFiles.RenameFiles()
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that rename operations were performed
|
||||
c.Check(s.publishedStorage.RenameOperations["old/path"], Equals, "new/path")
|
||||
c.Check(s.publishedStorage.RenameOperations["another/old"], Equals, "another/new")
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestRenameFilesError(c *C) {
|
||||
// Test rename error handling
|
||||
s.publishedStorage.SimulateRenameError = true
|
||||
s.indexFiles.renameMap["will/fail"] = "target"
|
||||
|
||||
err := s.indexFiles.RenameFiles()
|
||||
c.Check(err, NotNil)
|
||||
c.Check(err.Error(), Matches, ".*unable to rename.*")
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestAcquireByHashFeature(c *C) {
|
||||
// Test acquire-by-hash functionality
|
||||
s.indexFiles.acquireByHash = true
|
||||
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
compressable: true,
|
||||
acquireByHash: true,
|
||||
}
|
||||
|
||||
writer, _ := file.BufWriter()
|
||||
writer.WriteString("Package: test-hash\nVersion: 1.0\n")
|
||||
|
||||
err := file.Finalize(nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that by-hash directories were created
|
||||
c.Check(s.publishedStorage.dirs["dists/test/main/binary-amd64/by-hash/MD5Sum"], Equals, true)
|
||||
c.Check(s.publishedStorage.dirs["dists/test/main/binary-amd64/by-hash/SHA1"], Equals, true)
|
||||
c.Check(s.publishedStorage.dirs["dists/test/main/binary-amd64/by-hash/SHA256"], Equals, true)
|
||||
c.Check(s.publishedStorage.dirs["dists/test/main/binary-amd64/by-hash/SHA512"], Equals, true)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestPackageIndexByHashFunction(c *C) {
|
||||
// Test packageIndexByHash function directly
|
||||
s.indexFiles.generatedFiles["main/binary-amd64/Packages"] = utils.ChecksumInfo{
|
||||
MD5: "d41d8cd98f00b204e9800998ecf8427e",
|
||||
SHA1: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
SHA256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
SHA512: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
|
||||
}
|
||||
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
}
|
||||
|
||||
err := packageIndexByHash(file, "", "SHA256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Check that hard link was created
|
||||
expectedSrc := "dists/test/main/binary-amd64/Packages"
|
||||
expectedDst := "dists/test/main/binary-amd64/by-hash/SHA256/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
c.Check(s.publishedStorage.HardLinks[expectedDst], Equals, expectedSrc)
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestSkipBz2Feature(c *C) {
|
||||
// Test skipBz2 functionality
|
||||
s.indexFiles.skipBz2 = true
|
||||
|
||||
file := &indexFile{
|
||||
parent: s.indexFiles,
|
||||
relativePath: "main/binary-amd64/Packages",
|
||||
compressable: true,
|
||||
onlyGzip: false,
|
||||
}
|
||||
|
||||
writer, _ := file.BufWriter()
|
||||
writer.WriteString("Package: no-bz2\n")
|
||||
|
||||
err := file.Finalize(nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
// Should have .gz but not .bz2
|
||||
c.Check(s.publishedStorage.files["dists/test/main/binary-amd64/Packages.gz"], NotNil)
|
||||
_, hasBz2 := s.publishedStorage.files["dists/test/main/binary-amd64/Packages.bz2"]
|
||||
c.Check(hasBz2, Equals, false)
|
||||
}
|
||||
|
||||
// Mock implementations for testing
|
||||
|
||||
type MockPublishedStorage struct {
|
||||
files map[string]string
|
||||
dirs map[string]bool
|
||||
links map[string]string
|
||||
symlinks map[string]string
|
||||
HardLinks map[string]string
|
||||
RenameOperations map[string]string
|
||||
SimulateRenameError bool
|
||||
SimulateFileError bool
|
||||
SimulateSymlinkExists bool
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) MkDir(path string) error {
|
||||
if m.dirs == nil {
|
||||
m.dirs = make(map[string]bool)
|
||||
}
|
||||
m.dirs[path] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) PutFile(path, source string) error {
|
||||
if m.SimulateFileError {
|
||||
return fmt.Errorf("simulated file error")
|
||||
}
|
||||
if m.files == nil {
|
||||
m.files = make(map[string]string)
|
||||
}
|
||||
// Read source content (simplified for test)
|
||||
content, err := ioutil.ReadFile(source)
|
||||
if err != nil {
|
||||
// Create dummy content for missing files
|
||||
content = []byte("mock content")
|
||||
}
|
||||
m.files[path] = string(content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) Remove(path string) error {
|
||||
delete(m.files, path)
|
||||
delete(m.links, path)
|
||||
delete(m.symlinks, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) RenameFile(oldName, newName string) error {
|
||||
if m.SimulateRenameError {
|
||||
return fmt.Errorf("simulated rename error")
|
||||
}
|
||||
if m.RenameOperations == nil {
|
||||
m.RenameOperations = make(map[string]string)
|
||||
}
|
||||
m.RenameOperations[oldName] = newName
|
||||
if content, exists := m.files[oldName]; exists {
|
||||
m.files[newName] = content
|
||||
delete(m.files, oldName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) FileExists(path string) (bool, error) {
|
||||
_, exists := m.files[path]
|
||||
if !exists {
|
||||
_, exists = m.symlinks[path]
|
||||
}
|
||||
if m.SimulateSymlinkExists {
|
||||
return true, nil
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) HardLink(src, dst string) error {
|
||||
if m.HardLinks == nil {
|
||||
m.HardLinks = make(map[string]string)
|
||||
}
|
||||
m.HardLinks[dst] = src
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) SymLink(src, dst string) error {
|
||||
if m.symlinks == nil {
|
||||
m.symlinks = make(map[string]string)
|
||||
}
|
||||
m.symlinks[dst] = src
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) ReadLink(path string) (string, error) {
|
||||
if target, exists := m.symlinks[path]; exists {
|
||||
return target, nil
|
||||
}
|
||||
return "", fmt.Errorf("not a symlink")
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) Filelist(prefix string) ([]string, error) {
|
||||
var files []string
|
||||
for path := range m.files {
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
files = append(files, path)
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
||||
// Mock implementation - just track that it was called
|
||||
if m.files == nil {
|
||||
m.files = make(map[string]string)
|
||||
}
|
||||
m.files[publishedRelPath] = "linked from pool"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPublishedStorage) RemoveDirs(path string, progress aptly.Progress) error {
|
||||
// Mock implementation - remove files with path prefix
|
||||
for filePath := range m.files {
|
||||
if strings.HasPrefix(filePath, path) {
|
||||
delete(m.files, filePath)
|
||||
}
|
||||
}
|
||||
for dirPath := range m.dirs {
|
||||
if strings.HasPrefix(dirPath, path) {
|
||||
delete(m.dirs, dirPath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MockSigner struct {
|
||||
DetachedSignCalled bool
|
||||
ClearSignCalled bool
|
||||
}
|
||||
|
||||
func (m *MockSigner) Init() error { return nil }
|
||||
func (m *MockSigner) SetKey(keyRef string) {}
|
||||
func (m *MockSigner) SetKeyRing(keyring, secretKeyring string) {}
|
||||
func (m *MockSigner) SetPassphrase(passphrase, passphraseFile string) {}
|
||||
func (m *MockSigner) SetBatch(batch bool) {}
|
||||
|
||||
func (m *MockSigner) DetachedSign(source, signature string) error {
|
||||
m.DetachedSignCalled = true
|
||||
// Create mock signature file
|
||||
return ioutil.WriteFile(signature, []byte("mock signature"), 0644)
|
||||
}
|
||||
|
||||
func (m *MockSigner) ClearSign(source, signature string) error {
|
||||
m.ClearSignCalled = true
|
||||
// Create mock clear-signed file
|
||||
return ioutil.WriteFile(signature, []byte("mock clear signature"), 0644)
|
||||
}
|
||||
|
||||
type MockProgress struct {
|
||||
InitBarCalled bool
|
||||
ShutdownBarCalled bool
|
||||
AddBarCount int
|
||||
}
|
||||
|
||||
func (m *MockProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {
|
||||
m.InitBarCalled = true
|
||||
}
|
||||
|
||||
func (m *MockProgress) ShutdownBar() {
|
||||
m.ShutdownBarCalled = true
|
||||
}
|
||||
|
||||
func (m *MockProgress) AddBar(count int) {
|
||||
m.AddBarCount += count
|
||||
}
|
||||
|
||||
func (m *MockProgress) SetBar(count int) {}
|
||||
|
||||
func (m *MockProgress) PrintfBar(msg string, a ...interface{}) {}
|
||||
|
||||
func (m *MockProgress) ColoredPrintf(msg string, a ...interface{}) {}
|
||||
|
||||
func (m *MockProgress) Printf(msg string, a ...interface{}) {}
|
||||
|
||||
func (m *MockProgress) Flush() {}
|
||||
|
||||
func (m *MockProgress) PrintfStdErr(msg string, a ...interface{}) {}
|
||||
|
||||
func (m *MockProgress) Start() {}
|
||||
|
||||
func (m *MockProgress) Shutdown() {}
|
||||
|
||||
func (m *MockProgress) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type PackageDependenciesSuite struct{}
|
||||
|
||||
var _ = Suite(&PackageDependenciesSuite{})
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesBasic(c *C) {
|
||||
// Test basic dependency parsing with single dependency
|
||||
stanza := Stanza{
|
||||
"Depends": "package1",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"package1"})
|
||||
|
||||
// Check that key was removed from stanza
|
||||
_, exists := stanza["Depends"]
|
||||
c.Check(exists, Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesMultiple(c *C) {
|
||||
// Test parsing multiple dependencies separated by commas
|
||||
stanza := Stanza{
|
||||
"Depends": "package1, package2, package3",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"package1", "package2", "package3"})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesWithVersions(c *C) {
|
||||
// Test parsing dependencies with version constraints
|
||||
stanza := Stanza{
|
||||
"Depends": "package1 (>= 1.0), package2 (<< 2.0), package3 (= 1.5)",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"package1 (>= 1.0)", "package2 (<< 2.0)", "package3 (= 1.5)"})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesWithWhitespace(c *C) {
|
||||
// Test parsing dependencies with various whitespace patterns
|
||||
stanza := Stanza{
|
||||
"Depends": " package1 , package2 ,package3, package4 ",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"package1", "package2", "package3", "package4"})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesEmpty(c *C) {
|
||||
// Test parsing empty dependency string
|
||||
stanza := Stanza{
|
||||
"Depends": "",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, IsNil)
|
||||
|
||||
// Check that key was removed from stanza
|
||||
_, exists := stanza["Depends"]
|
||||
c.Check(exists, Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesWhitespaceOnly(c *C) {
|
||||
// Test parsing dependency string with only whitespace
|
||||
stanza := Stanza{
|
||||
"Depends": " \t \n ",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, IsNil)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesMissingKey(c *C) {
|
||||
// Test parsing when key doesn't exist in stanza
|
||||
stanza := Stanza{
|
||||
"SomeOtherField": "value",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, IsNil)
|
||||
|
||||
// Check that original stanza is unchanged
|
||||
_, exists := stanza["SomeOtherField"]
|
||||
c.Check(exists, Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesComplexFormat(c *C) {
|
||||
// Test parsing complex dependency formats
|
||||
stanza := Stanza{
|
||||
"Depends": "libc6 (>= 2.17), libgcc1 (>= 1:4.1.1), libstdc++6 (>= 4.8.1)",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
expected := []string{
|
||||
"libc6 (>= 2.17)",
|
||||
"libgcc1 (>= 1:4.1.1)",
|
||||
"libstdc++6 (>= 4.8.1)",
|
||||
}
|
||||
c.Check(result, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesAlternatives(c *C) {
|
||||
// Test parsing dependencies with alternatives (| separator within single dependency)
|
||||
stanza := Stanza{
|
||||
"Depends": "mail-transport-agent | postfix, libc6 (>= 2.17)",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
expected := []string{
|
||||
"mail-transport-agent | postfix",
|
||||
"libc6 (>= 2.17)",
|
||||
}
|
||||
c.Check(result, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesSpecialCharacters(c *C) {
|
||||
// Test parsing dependencies with special characters in package names
|
||||
stanza := Stanza{
|
||||
"Depends": "lib-package++-dev, package.name, package_underscore",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
expected := []string{
|
||||
"lib-package++-dev",
|
||||
"package.name",
|
||||
"package_underscore",
|
||||
}
|
||||
c.Check(result, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesArchitectures(c *C) {
|
||||
// Test parsing dependencies with architecture specifications
|
||||
stanza := Stanza{
|
||||
"Depends": "package1 [amd64], package2 [!arm64], package3 [i386 amd64]",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
expected := []string{
|
||||
"package1 [amd64]",
|
||||
"package2 [!arm64]",
|
||||
"package3 [i386 amd64]",
|
||||
}
|
||||
c.Check(result, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesProfiles(c *C) {
|
||||
// Test parsing dependencies with build profiles
|
||||
stanza := Stanza{
|
||||
"Depends": "package1 <cross>, package2 <!nocheck>, package3 <stage1 !cross>",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
expected := []string{
|
||||
"package1 <cross>",
|
||||
"package2 <!nocheck>",
|
||||
"package3 <stage1 !cross>",
|
||||
}
|
||||
c.Check(result, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesLongLine(c *C) {
|
||||
// Test parsing very long dependency line
|
||||
longDeps := "pkg1, pkg2, pkg3, pkg4, pkg5, pkg6, pkg7, pkg8, pkg9, pkg10, " +
|
||||
"pkg11, pkg12, pkg13, pkg14, pkg15, pkg16, pkg17, pkg18, pkg19, pkg20"
|
||||
|
||||
stanza := Stanza{
|
||||
"Depends": longDeps,
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(len(result), Equals, 20)
|
||||
c.Check(result[0], Equals, "pkg1")
|
||||
c.Check(result[19], Equals, "pkg20")
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesSingleComma(c *C) {
|
||||
// Test edge case with single comma
|
||||
stanza := Stanza{
|
||||
"Depends": ",",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"", ""})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesTrailingComma(c *C) {
|
||||
// Test with trailing comma
|
||||
stanza := Stanza{
|
||||
"Depends": "package1, package2,",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"package1", "package2", ""})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesLeadingComma(c *C) {
|
||||
// Test with leading comma
|
||||
stanza := Stanza{
|
||||
"Depends": ", package1, package2",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"", "package1", "package2"})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesMultipleCommas(c *C) {
|
||||
// Test with multiple consecutive commas
|
||||
stanza := Stanza{
|
||||
"Depends": "package1,, package2,,, package3",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"package1", "", "package2", "", "", "package3"})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesRealWorld(c *C) {
|
||||
// Test with real-world dependency examples
|
||||
stanza := Stanza{
|
||||
"Depends": "debconf (>= 0.5) | debconf-2.0, libc6 (>= 2.14), libgcc1 (>= 1:3.0), libstdc++6 (>= 5.2)",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
expected := []string{
|
||||
"debconf (>= 0.5) | debconf-2.0",
|
||||
"libc6 (>= 2.14)",
|
||||
"libgcc1 (>= 1:3.0)",
|
||||
"libstdc++6 (>= 5.2)",
|
||||
}
|
||||
c.Check(result, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesDifferentKeys(c *C) {
|
||||
// Test parsing different dependency types
|
||||
stanza := Stanza{
|
||||
"Depends": "runtime-dep",
|
||||
"Build-Depends": "build-dep",
|
||||
"Build-Depends-Indep": "build-indep-dep",
|
||||
"Pre-Depends": "pre-dep",
|
||||
"Suggests": "suggest-dep",
|
||||
"Recommends": "recommend-dep",
|
||||
}
|
||||
|
||||
// Test each dependency type
|
||||
depends := parseDependencies(stanza, "Depends")
|
||||
c.Check(depends, DeepEquals, []string{"runtime-dep"})
|
||||
|
||||
buildDepends := parseDependencies(stanza, "Build-Depends")
|
||||
c.Check(buildDepends, DeepEquals, []string{"build-dep"})
|
||||
|
||||
buildDependsIndep := parseDependencies(stanza, "Build-Depends-Indep")
|
||||
c.Check(buildDependsIndep, DeepEquals, []string{"build-indep-dep"})
|
||||
|
||||
preDepends := parseDependencies(stanza, "Pre-Depends")
|
||||
c.Check(preDepends, DeepEquals, []string{"pre-dep"})
|
||||
|
||||
suggests := parseDependencies(stanza, "Suggests")
|
||||
c.Check(suggests, DeepEquals, []string{"suggest-dep"})
|
||||
|
||||
recommends := parseDependencies(stanza, "Recommends")
|
||||
c.Check(recommends, DeepEquals, []string{"recommend-dep"})
|
||||
|
||||
// Verify all keys were removed
|
||||
c.Check(len(stanza), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestPackageDependenciesStruct(c *C) {
|
||||
// Test PackageDependencies struct creation and field access
|
||||
deps := PackageDependencies{
|
||||
Depends: []string{"dep1", "dep2"},
|
||||
BuildDepends: []string{"build-dep1", "build-dep2"},
|
||||
BuildDependsInDep: []string{"build-indep-dep1"},
|
||||
PreDepends: []string{"pre-dep1"},
|
||||
Suggests: []string{"suggest1", "suggest2"},
|
||||
Recommends: []string{"recommend1"},
|
||||
}
|
||||
|
||||
c.Check(deps.Depends, DeepEquals, []string{"dep1", "dep2"})
|
||||
c.Check(deps.BuildDepends, DeepEquals, []string{"build-dep1", "build-dep2"})
|
||||
c.Check(deps.BuildDependsInDep, DeepEquals, []string{"build-indep-dep1"})
|
||||
c.Check(deps.PreDepends, DeepEquals, []string{"pre-dep1"})
|
||||
c.Check(deps.Suggests, DeepEquals, []string{"suggest1", "suggest2"})
|
||||
c.Check(deps.Recommends, DeepEquals, []string{"recommend1"})
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesUnicodeCharacters(c *C) {
|
||||
// Test parsing dependencies with unicode characters
|
||||
stanza := Stanza{
|
||||
"Depends": "libμ-package, package-ñoño, 中文-package",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
expected := []string{
|
||||
"libμ-package",
|
||||
"package-ñoño",
|
||||
"中文-package",
|
||||
}
|
||||
c.Check(result, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesStanzaImmutability(c *C) {
|
||||
// Test that original stanza values are not modified (except for key removal)
|
||||
original := Stanza{
|
||||
"Depends": "package1, package2",
|
||||
"Other": "value",
|
||||
}
|
||||
|
||||
// Make a copy to compare
|
||||
stanza := Stanza{
|
||||
"Depends": original["Depends"],
|
||||
"Other": original["Other"],
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, DeepEquals, []string{"package1", "package2"})
|
||||
|
||||
// Check that Depends key was removed but Other remains unchanged
|
||||
_, dependsExists := stanza["Depends"]
|
||||
c.Check(dependsExists, Equals, false)
|
||||
c.Check(stanza["Other"], Equals, original["Other"])
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesEmptyStanza(c *C) {
|
||||
// Test with completely empty stanza
|
||||
stanza := Stanza{}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
c.Check(result, IsNil)
|
||||
c.Check(len(stanza), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *PackageDependenciesSuite) TestParseDependenciesTabsAndNewlines(c *C) {
|
||||
// Test parsing dependencies with tabs and newlines
|
||||
stanza := Stanza{
|
||||
"Depends": "package1,\n\tpackage2,\t package3\n,package4",
|
||||
}
|
||||
|
||||
result := parseDependencies(stanza, "Depends")
|
||||
// The function should handle tabs and newlines as whitespace
|
||||
c.Check(len(result), Equals, 4)
|
||||
c.Check(result[0], Equals, "package1")
|
||||
c.Check(result[3], Equals, "package4")
|
||||
}
|
||||
Reference in New Issue
Block a user