mirror of
https://github.com/gabrie30/ghorg.git
synced 2026-01-21 16:21:43 +01:00
- Implement SyncDefaultBranch function with 4 safety checks - Add 8 git helper functions (GetRemoteURL, HasLocalChanges, etc.) - Add 640 lines of unit tests for git helpers (git_test.go) - Add 2074 lines of sync tests with 45+ scenarios (sync_test.go) - Integrate GHORG_SYNC_DEFAULT_BRANCH environment variable - Add 8 testing targets to Makefile - Update README.md with comprehensive sync feature documentation - Update sample-conf.yaml with detailed configuration comments - Add test coverage verification document Safety checks implemented: - Skips sync if uncommitted local changes - Skips sync if unpushed commits - Skips sync if commits not on default branch - Skips sync if default branch diverged from HEAD Test coverage: 51.6% overall, 76-100% on new functions
729 lines
20 KiB
Go
729 lines
20 KiB
Go
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/gabrie30/ghorg/scm"
|
|
)
|
|
|
|
// ExtendedMockGitClient extends the existing MockGitClient with additional methods needed for RepositoryProcessor
|
|
type ExtendedMockGitClient struct {
|
|
MockGitClient
|
|
shouldFailClone bool
|
|
shouldFailCheckout bool
|
|
shouldFailSetOrigin bool
|
|
shouldReturnEmptyRepo bool
|
|
preCommitCount int
|
|
postCommitCount int
|
|
}
|
|
|
|
func NewExtendedMockGit() *ExtendedMockGitClient {
|
|
return &ExtendedMockGitClient{
|
|
MockGitClient: NewMockGit(),
|
|
preCommitCount: 5,
|
|
postCommitCount: 7,
|
|
}
|
|
}
|
|
|
|
func (g *ExtendedMockGitClient) Clone(repo scm.Repo) error {
|
|
if g.shouldFailClone {
|
|
return errors.New("mock clone error")
|
|
}
|
|
return g.MockGitClient.Clone(repo)
|
|
}
|
|
|
|
func (g *ExtendedMockGitClient) Checkout(repo scm.Repo) error {
|
|
if g.shouldFailCheckout {
|
|
return errors.New("mock checkout error")
|
|
}
|
|
if g.shouldReturnEmptyRepo {
|
|
return errors.New("Cannot checkout any specific branch in an empty repository")
|
|
}
|
|
return g.MockGitClient.Checkout(repo)
|
|
}
|
|
|
|
func (g *ExtendedMockGitClient) SetOrigin(repo scm.Repo) error {
|
|
if g.shouldFailSetOrigin {
|
|
return errors.New("mock set origin error")
|
|
}
|
|
return g.MockGitClient.SetOrigin(repo)
|
|
}
|
|
|
|
func (g *ExtendedMockGitClient) RepoCommitCount(repo scm.Repo) (int, error) {
|
|
// First call returns pre-pull count, second call returns post-pull count
|
|
if repo.Commits.CountPrePull == 0 {
|
|
return g.preCommitCount, nil
|
|
}
|
|
return g.postCommitCount, nil
|
|
}
|
|
|
|
func (g *ExtendedMockGitClient) SyncDefaultBranch(repo scm.Repo) error {
|
|
return nil
|
|
}
|
|
|
|
func TestRepositoryProcessor_NewRepositoryProcessor(t *testing.T) {
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
if processor == nil {
|
|
t.Fatal("Expected processor to be created")
|
|
}
|
|
|
|
if processor.git != mockGit {
|
|
t.Error("Expected git client to be set correctly")
|
|
}
|
|
|
|
if processor.stats == nil {
|
|
t.Error("Expected stats to be initialized")
|
|
}
|
|
|
|
if processor.mutex == nil {
|
|
t.Error("Expected mutex to be initialized")
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_NewRepository(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
// Set up temporary directory
|
|
dir, err := os.MkdirTemp("", "ghorg_test_process_new_repo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
stats := processor.GetStats()
|
|
if stats.CloneCount != 1 {
|
|
t.Errorf("Expected clone count to be 1, got %d", stats.CloneCount)
|
|
}
|
|
|
|
if stats.PulledCount != 0 {
|
|
t.Errorf("Expected pulled count to be 0, got %d", stats.PulledCount)
|
|
}
|
|
|
|
if len(stats.CloneErrors) != 0 {
|
|
t.Errorf("Expected no clone errors, got %v", stats.CloneErrors)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_ExistingRepository(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
// Set up temporary directory with existing repo
|
|
dir, err := os.MkdirTemp("", "ghorg_test_process_existing_repo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
// Create existing repo directory
|
|
repoDir := filepath.Join(dir, "test-repo")
|
|
err = os.MkdirAll(repoDir, 0755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
HostPath: repoDir,
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
stats := processor.GetStats()
|
|
if stats.CloneCount != 0 {
|
|
t.Errorf("Expected clone count to be 0, got %d", stats.CloneCount)
|
|
}
|
|
|
|
if stats.PulledCount != 1 {
|
|
t.Errorf("Expected pulled count to be 1, got %d", stats.PulledCount)
|
|
}
|
|
|
|
// Check that commit diff was calculated
|
|
if stats.NewCommits != (mockGit.postCommitCount - mockGit.preCommitCount) {
|
|
t.Errorf("Expected new commits to be %d, got %d",
|
|
mockGit.postCommitCount-mockGit.preCommitCount, stats.NewCommits)
|
|
}
|
|
|
|
// Verify that CountDiff was properly calculated on the repo
|
|
if repo.Commits.CountDiff != (mockGit.postCommitCount - mockGit.preCommitCount) {
|
|
t.Errorf("Expected repo CountDiff to be %d, got %d",
|
|
mockGit.postCommitCount-mockGit.preCommitCount, repo.Commits.CountDiff)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_CloneError(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
dir, err := os.MkdirTemp("", "ghorg_test_process_clone_error")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
mockGit.shouldFailClone = true
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
stats := processor.GetStats()
|
|
if stats.CloneCount != 0 {
|
|
t.Errorf("Expected clone count to be 0, got %d", stats.CloneCount)
|
|
}
|
|
|
|
if len(stats.CloneErrors) != 1 {
|
|
t.Errorf("Expected 1 clone error, got %d", len(stats.CloneErrors))
|
|
}
|
|
|
|
if stats.CloneErrors[0] == "" {
|
|
t.Error("Expected error message to be set")
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_WikiHandling(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
dir, err := os.MkdirTemp("", "ghorg_test_process_wiki")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
mockGit.shouldFailClone = true // Simulate wiki with no content
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo.wiki",
|
|
CloneBranch: "main",
|
|
IsWiki: true,
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo.wiki", 0)
|
|
|
|
stats := processor.GetStats()
|
|
if len(stats.CloneInfos) != 1 {
|
|
t.Errorf("Expected 1 clone info message, got %d", len(stats.CloneInfos))
|
|
}
|
|
|
|
if len(stats.CloneErrors) != 0 {
|
|
t.Errorf("Expected no clone errors for wiki, got %d", len(stats.CloneErrors))
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_BackupMode(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
os.Setenv("GHORG_BACKUP", "true")
|
|
|
|
// Set up temporary directory with existing repo
|
|
dir, err := os.MkdirTemp("", "ghorg_test_backup_mode")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
// Create existing repo directory
|
|
repoDir := filepath.Join(dir, "test-repo")
|
|
err = os.MkdirAll(repoDir, 0755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
HostPath: repoDir,
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
stats := processor.GetStats()
|
|
if stats.UpdateRemoteCount != 1 {
|
|
t.Errorf("Expected update remote count to be 1, got %d", stats.UpdateRemoteCount)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_NoCleanMode(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
os.Setenv("GHORG_NO_CLEAN", "true")
|
|
|
|
// Set up temporary directory with existing repo
|
|
dir, err := os.MkdirTemp("", "ghorg_test_no_clean_mode")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
// Create existing repo directory
|
|
repoDir := filepath.Join(dir, "test-repo")
|
|
err = os.MkdirAll(repoDir, 0755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
HostPath: repoDir,
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
stats := processor.GetStats()
|
|
// In no-clean mode, we still increment pulled count
|
|
if stats.PulledCount != 1 {
|
|
t.Errorf("Expected pulled count to be 1, got %d", stats.PulledCount)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_NoCleanModeWithFetchAllDisabled(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
os.Setenv("GHORG_NO_CLEAN", "true")
|
|
os.Setenv("GHORG_FETCH_ALL", "false")
|
|
|
|
// Set up temporary directory with existing repo
|
|
dir, err := os.MkdirTemp("", "ghorg_test_no_clean_fetch_all_disabled")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
// Create existing repo directory
|
|
repoDir := filepath.Join(dir, "test-repo")
|
|
err = os.MkdirAll(repoDir, 0755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
HostPath: repoDir,
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
stats := processor.GetStats()
|
|
// In no-clean mode with fetch-all disabled, we should still process successfully
|
|
if stats.PulledCount != 1 {
|
|
t.Errorf("Expected pulled count to be 1, got %d", stats.PulledCount)
|
|
}
|
|
// Should not have any errors since fetch-all is skipped when disabled
|
|
if len(stats.CloneErrors) != 0 {
|
|
t.Errorf("Expected no errors when FETCH_ALL is disabled, got %d errors: %v", len(stats.CloneErrors), stats.CloneErrors)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_NoCleanModeWithFetchAllEnabled(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
os.Setenv("GHORG_NO_CLEAN", "true")
|
|
os.Setenv("GHORG_FETCH_ALL", "true")
|
|
|
|
// Set up temporary directory with existing repo
|
|
dir, err := os.MkdirTemp("", "ghorg_test_no_clean_fetch_all_enabled")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
// Create existing repo directory
|
|
repoDir := filepath.Join(dir, "test-repo")
|
|
err = os.MkdirAll(repoDir, 0755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
HostPath: repoDir,
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
stats := processor.GetStats()
|
|
// In no-clean mode with fetch-all enabled, we should still process successfully
|
|
if stats.PulledCount != 1 {
|
|
t.Errorf("Expected pulled count to be 1, got %d", stats.PulledCount)
|
|
}
|
|
// Should not have any errors since fetch-all is enabled and mocked
|
|
if len(stats.CloneErrors) != 0 {
|
|
t.Errorf("Expected no errors when FETCH_ALL is enabled, got %d errors: %v", len(stats.CloneErrors), stats.CloneErrors)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_NameCollisions(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
dir, err := os.MkdirTemp("", "ghorg_test_name_collisions")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
Path: "group/subgroup/test-repo",
|
|
}
|
|
|
|
repoNameWithCollisions := map[string]bool{
|
|
"test-repo": true,
|
|
}
|
|
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, true, "test-repo", 1)
|
|
|
|
// Check that the repo was processed despite collisions
|
|
stats := processor.GetStats()
|
|
if stats.CloneCount != 1 {
|
|
t.Errorf("Expected clone count to be 1, got %d", stats.CloneCount)
|
|
}
|
|
|
|
// The host path should be modified due to collision handling
|
|
expectedPath := filepath.Join(outputDirAbsolutePath, "group_subgroup_test-repo")
|
|
if repo.HostPath != expectedPath {
|
|
t.Errorf("Expected host path to be modified for collisions, got %s", repo.HostPath)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_CrossPlatformPaths(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
dir, err := os.MkdirTemp("", "ghorg_test_cross_platform")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
// Test with forward slashes (Unix-style)
|
|
repoUnix := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://github.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
Path: "group/subgroup/test-repo",
|
|
}
|
|
|
|
// Test with backslashes (Windows-style)
|
|
repoWindows := scm.Repo{
|
|
Name: "test-repo2",
|
|
URL: "https://github.com/org/test-repo2",
|
|
CloneBranch: "main",
|
|
Path: "group\\subgroup\\test-repo2",
|
|
}
|
|
|
|
repoNameWithCollisions := map[string]bool{
|
|
"test-repo": true,
|
|
"test-repo2": true,
|
|
}
|
|
|
|
// Process Unix-style path
|
|
processor.ProcessRepository(&repoUnix, repoNameWithCollisions, true, "test-repo", 0)
|
|
expectedUnixPath := filepath.Join(outputDirAbsolutePath, "group_subgroup_test-repo")
|
|
if repoUnix.HostPath != expectedUnixPath {
|
|
t.Errorf("Expected Unix-style path to be %s, got %s", expectedUnixPath, repoUnix.HostPath)
|
|
}
|
|
|
|
// Process Windows-style path
|
|
processor.ProcessRepository(&repoWindows, repoNameWithCollisions, true, "test-repo2", 1)
|
|
expectedWindowsPath := filepath.Join(outputDirAbsolutePath, "group_subgroup_test-repo2")
|
|
if repoWindows.HostPath != expectedWindowsPath {
|
|
t.Errorf("Expected Windows-style path to be %s, got %s", expectedWindowsPath, repoWindows.HostPath)
|
|
}
|
|
|
|
stats := processor.GetStats()
|
|
if stats.CloneCount != 2 {
|
|
t.Errorf("Expected clone count to be 2, got %d", stats.CloneCount)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ProcessRepository_GitLabSnippets(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
dir, err := os.MkdirTemp("", "ghorg_test_gitlab_snippets")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
// Test regular snippet
|
|
repo := scm.Repo{
|
|
Name: "test-repo",
|
|
URL: "https://gitlab.com/org/test-repo",
|
|
CloneBranch: "main",
|
|
IsGitLabSnippet: true,
|
|
GitLabSnippetInfo: scm.GitLabSnippet{
|
|
Title: "My Snippet",
|
|
ID: "123",
|
|
URLOfRepo: "https://gitlab.com/org/test-repo.git",
|
|
},
|
|
}
|
|
|
|
repoNameWithCollisions := make(map[string]bool)
|
|
processor.ProcessRepository(&repo, repoNameWithCollisions, false, "test-repo", 0)
|
|
|
|
expectedPath := filepath.Join(outputDirAbsolutePath, "test-repo.snippets", "My Snippet-123")
|
|
if repo.HostPath != expectedPath {
|
|
t.Errorf("Expected host path %s, got %s", expectedPath, repo.HostPath)
|
|
}
|
|
|
|
// Test root level snippet
|
|
rootSnippetRepo := scm.Repo{
|
|
Name: "root-snippet",
|
|
URL: "https://gitlab.com/snippets/456",
|
|
CloneBranch: "main",
|
|
IsGitLabSnippet: true,
|
|
IsGitLabRootLevelSnippet: true,
|
|
GitLabSnippetInfo: scm.GitLabSnippet{
|
|
Title: "Root Snippet",
|
|
ID: "456",
|
|
},
|
|
}
|
|
|
|
processor.ProcessRepository(&rootSnippetRepo, repoNameWithCollisions, false, "root-snippet", 0)
|
|
|
|
expectedRootPath := filepath.Join(outputDirAbsolutePath, "_ghorg_root_level_snippets", "Root Snippet-456")
|
|
if rootSnippetRepo.HostPath != expectedRootPath {
|
|
t.Errorf("Expected host path %s, got %s", expectedRootPath, rootSnippetRepo.HostPath)
|
|
}
|
|
|
|
stats := processor.GetStats()
|
|
if stats.CloneCount != 2 {
|
|
t.Errorf("Expected clone count to be 2, got %d", stats.CloneCount)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_GetStats(t *testing.T) {
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
// Add some stats manually
|
|
processor.addError("test error")
|
|
processor.addInfo("test info")
|
|
|
|
stats := processor.GetStats()
|
|
|
|
if len(stats.CloneErrors) != 1 {
|
|
t.Errorf("Expected 1 clone error, got %d", len(stats.CloneErrors))
|
|
}
|
|
|
|
if stats.CloneErrors[0] != "test error" {
|
|
t.Errorf("Expected error message 'test error', got '%s'", stats.CloneErrors[0])
|
|
}
|
|
|
|
if len(stats.CloneInfos) != 1 {
|
|
t.Errorf("Expected 1 clone info, got %d", len(stats.CloneInfos))
|
|
}
|
|
|
|
if stats.CloneInfos[0] != "test info" {
|
|
t.Errorf("Expected info message 'test info', got '%s'", stats.CloneInfos[0])
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_ThreadSafety(t *testing.T) {
|
|
defer UnsetEnv("GHORG_")()
|
|
|
|
dir, err := os.MkdirTemp("", "ghorg_test_thread_safety")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
outputDirAbsolutePath = dir
|
|
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
// Simulate concurrent access
|
|
numGoroutines := 10
|
|
done := make(chan bool, numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(index int) {
|
|
processor.addError("error " + string(rune(index)))
|
|
processor.addInfo("info " + string(rune(index)))
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines to complete
|
|
for i := 0; i < numGoroutines; i++ {
|
|
<-done
|
|
}
|
|
|
|
stats := processor.GetStats()
|
|
if len(stats.CloneErrors) != numGoroutines {
|
|
t.Errorf("Expected %d clone errors, got %d", numGoroutines, len(stats.CloneErrors))
|
|
}
|
|
|
|
if len(stats.CloneInfos) != numGoroutines {
|
|
t.Errorf("Expected %d clone infos, got %d", numGoroutines, len(stats.CloneInfos))
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_SetTotalDuration(t *testing.T) {
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
// Test setting timing
|
|
processor.SetTotalDuration(42)
|
|
|
|
stats := processor.GetStats()
|
|
if stats.TotalDurationSeconds != 42 {
|
|
t.Errorf("Expected total duration to be 42, got %d", stats.TotalDurationSeconds)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryProcessor_SetTotalDuration_ThreadSafety(t *testing.T) {
|
|
mockGit := NewExtendedMockGit()
|
|
processor := NewRepositoryProcessor(mockGit)
|
|
|
|
// Test concurrent timing updates
|
|
numGoroutines := 10
|
|
done := make(chan bool, numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(index int) {
|
|
processor.SetTotalDuration(index * 10)
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines to complete
|
|
for i := 0; i < numGoroutines; i++ {
|
|
<-done
|
|
}
|
|
|
|
// The final value should be one of the set values (race condition, but still valid)
|
|
stats := processor.GetStats()
|
|
validValues := make(map[int]bool)
|
|
for i := 0; i < numGoroutines; i++ {
|
|
validValues[i*10] = true
|
|
}
|
|
|
|
if !validValues[stats.TotalDurationSeconds] {
|
|
t.Errorf("Expected total duration to be one of the set values, got %d", stats.TotalDurationSeconds)
|
|
}
|
|
}
|
|
|
|
func TestCloneStats_NewStruct(t *testing.T) {
|
|
stats := CloneStats{
|
|
CloneCount: 5,
|
|
PulledCount: 3,
|
|
UpdateRemoteCount: 2,
|
|
NewCommits: 10,
|
|
UntouchedPrunes: 1,
|
|
TotalDurationSeconds: 120,
|
|
CloneInfos: []string{"info1", "info2"},
|
|
CloneErrors: []string{"error1"},
|
|
}
|
|
|
|
// Verify all fields are properly set
|
|
if stats.CloneCount != 5 {
|
|
t.Errorf("Expected CloneCount to be 5, got %d", stats.CloneCount)
|
|
}
|
|
if stats.PulledCount != 3 {
|
|
t.Errorf("Expected PulledCount to be 3, got %d", stats.PulledCount)
|
|
}
|
|
if stats.UpdateRemoteCount != 2 {
|
|
t.Errorf("Expected UpdateRemoteCount to be 2, got %d", stats.UpdateRemoteCount)
|
|
}
|
|
if stats.NewCommits != 10 {
|
|
t.Errorf("Expected NewCommits to be 10, got %d", stats.NewCommits)
|
|
}
|
|
if stats.UntouchedPrunes != 1 {
|
|
t.Errorf("Expected UntouchedPrunes to be 1, got %d", stats.UntouchedPrunes)
|
|
}
|
|
if stats.TotalDurationSeconds != 120 {
|
|
t.Errorf("Expected TotalDurationSeconds to be 120, got %d", stats.TotalDurationSeconds)
|
|
}
|
|
if len(stats.CloneInfos) != 2 {
|
|
t.Errorf("Expected 2 CloneInfos, got %d", len(stats.CloneInfos))
|
|
}
|
|
if len(stats.CloneErrors) != 1 {
|
|
t.Errorf("Expected 1 CloneError, got %d", len(stats.CloneErrors))
|
|
}
|
|
}
|