Add unit tests to improve cmd/bd CLI coverage (bd-llfl)
Added 479 lines of new tests: - utils_unit_test.go: Tests for utility functions (truncateString, pluralize, formatTimeAgo, containsLabel, extractIDSuffix, truncate, truncateDescription, showCleanupDeprecationHint, clearAutoFlushState) - hooks_test.go: Tests for FormatHookWarnings, isRebaseInProgress, hasBeadsJSONL - list_test.go: Tests for formatIssueLong, formatIssueCompact - version_tracking_test.go: Fixed flaky tests by setting BEADS_DIR env var to prevent git worktree detection from finding the actual .beads directory instead of the temp directory Coverage increased from 21.6% to 22.0%. The remaining 78% of untested code involves daemon/RPC operations, command handlers requiring database/daemon setup, and git operations that would require integration tests with mocked dependencies. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -282,3 +282,120 @@ func TestInstallHooksShared(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormatHookWarnings(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
statuses []HookStatus
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no issues",
|
||||||
|
statuses: []HookStatus{{Name: "pre-commit", Installed: true}},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one missing",
|
||||||
|
statuses: []HookStatus{{Name: "pre-commit", Installed: false}},
|
||||||
|
want: "⚠️ Git hooks not installed (1 missing)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple missing",
|
||||||
|
statuses: []HookStatus{
|
||||||
|
{Name: "pre-commit", Installed: false},
|
||||||
|
{Name: "post-merge", Installed: false},
|
||||||
|
},
|
||||||
|
want: "⚠️ Git hooks not installed (2 missing)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one outdated",
|
||||||
|
statuses: []HookStatus{{Name: "pre-commit", Installed: true, Outdated: true}},
|
||||||
|
want: "⚠️ Git hooks are outdated (1 hooks)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed missing and outdated",
|
||||||
|
statuses: []HookStatus{
|
||||||
|
{Name: "pre-commit", Installed: false},
|
||||||
|
{Name: "post-merge", Installed: true, Outdated: true},
|
||||||
|
},
|
||||||
|
want: "⚠️ Git hooks not installed (1 missing)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := FormatHookWarnings(tt.statuses)
|
||||||
|
if tt.want == "" && got != "" {
|
||||||
|
t.Errorf("FormatHookWarnings() = %q, want empty", got)
|
||||||
|
} else if tt.want != "" && !strContains(got, tt.want) {
|
||||||
|
t.Errorf("FormatHookWarnings() = %q, want to contain %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func strContains(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || strContains(s[1:], substr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsRebaseInProgress(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
t.Chdir(tmpDir)
|
||||||
|
|
||||||
|
// Create .git directory
|
||||||
|
if err := os.MkdirAll(".git", 0755); err != nil {
|
||||||
|
t.Fatalf("Failed to create .git: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be false initially
|
||||||
|
if isRebaseInProgress() {
|
||||||
|
t.Error("isRebaseInProgress() = true, want false (no rebase marker)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create rebase-merge marker
|
||||||
|
if err := os.MkdirAll(".git/rebase-merge", 0755); err != nil {
|
||||||
|
t.Fatalf("Failed to create rebase-merge: %v", err)
|
||||||
|
}
|
||||||
|
if !isRebaseInProgress() {
|
||||||
|
t.Error("isRebaseInProgress() = false, want true (rebase-merge exists)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove rebase-merge
|
||||||
|
if err := os.RemoveAll(".git/rebase-merge"); err != nil {
|
||||||
|
t.Fatalf("Failed to remove rebase-merge: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create rebase-apply marker
|
||||||
|
if err := os.MkdirAll(".git/rebase-apply", 0755); err != nil {
|
||||||
|
t.Fatalf("Failed to create rebase-apply: %v", err)
|
||||||
|
}
|
||||||
|
if !isRebaseInProgress() {
|
||||||
|
t.Error("isRebaseInProgress() = false, want true (rebase-apply exists)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasBeadsJSONL(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
t.Chdir(tmpDir)
|
||||||
|
|
||||||
|
// Should be false initially (no .beads directory)
|
||||||
|
if hasBeadsJSONL() {
|
||||||
|
t.Error("hasBeadsJSONL() = true, want false (no .beads)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create .beads directory without any JSONL files
|
||||||
|
if err := os.MkdirAll(".beads", 0755); err != nil {
|
||||||
|
t.Fatalf("Failed to create .beads: %v", err)
|
||||||
|
}
|
||||||
|
if hasBeadsJSONL() {
|
||||||
|
t.Error("hasBeadsJSONL() = true, want false (no JSONL files)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create issues.jsonl
|
||||||
|
if err := os.WriteFile(".beads/issues.jsonl", []byte("{}"), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to create issues.jsonl: %v", err)
|
||||||
|
}
|
||||||
|
if !hasBeadsJSONL() {
|
||||||
|
t.Error("hasBeadsJSONL() = false, want true (issues.jsonl exists)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -456,6 +456,146 @@ func TestListQueryCapabilitiesSuite(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormatIssueLong(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
issue *types.Issue
|
||||||
|
labels []string
|
||||||
|
want string // substring to check for
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "open issue",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-123",
|
||||||
|
Title: "Test Issue",
|
||||||
|
Priority: 1,
|
||||||
|
IssueType: types.TypeBug,
|
||||||
|
Status: types.StatusOpen,
|
||||||
|
},
|
||||||
|
labels: nil,
|
||||||
|
want: "test-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "closed issue",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-456",
|
||||||
|
Title: "Closed Issue",
|
||||||
|
Priority: 0,
|
||||||
|
IssueType: types.TypeTask,
|
||||||
|
Status: types.StatusClosed,
|
||||||
|
},
|
||||||
|
labels: nil,
|
||||||
|
want: "test-456",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issue with assignee",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-789",
|
||||||
|
Title: "Assigned Issue",
|
||||||
|
Priority: 2,
|
||||||
|
IssueType: types.TypeFeature,
|
||||||
|
Status: types.StatusInProgress,
|
||||||
|
Assignee: "alice",
|
||||||
|
},
|
||||||
|
labels: nil,
|
||||||
|
want: "Assignee: alice",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issue with labels",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-abc",
|
||||||
|
Title: "Labeled Issue",
|
||||||
|
Priority: 1,
|
||||||
|
IssueType: types.TypeBug,
|
||||||
|
Status: types.StatusOpen,
|
||||||
|
},
|
||||||
|
labels: []string{"critical", "security"},
|
||||||
|
want: "Labels:",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var buf strings.Builder
|
||||||
|
formatIssueLong(&buf, tt.issue, tt.labels)
|
||||||
|
result := buf.String()
|
||||||
|
if !strings.Contains(result, tt.want) {
|
||||||
|
t.Errorf("formatIssueLong() = %q, want to contain %q", result, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatIssueCompact(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
issue *types.Issue
|
||||||
|
labels []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic issue",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-123",
|
||||||
|
Title: "Test Issue",
|
||||||
|
Priority: 1,
|
||||||
|
IssueType: types.TypeBug,
|
||||||
|
Status: types.StatusOpen,
|
||||||
|
},
|
||||||
|
labels: nil,
|
||||||
|
want: "Test Issue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issue with assignee",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-456",
|
||||||
|
Title: "Assigned Issue",
|
||||||
|
Priority: 2,
|
||||||
|
IssueType: types.TypeTask,
|
||||||
|
Status: types.StatusInProgress,
|
||||||
|
Assignee: "bob",
|
||||||
|
},
|
||||||
|
labels: nil,
|
||||||
|
want: "@bob",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issue with labels",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-789",
|
||||||
|
Title: "Labeled Issue",
|
||||||
|
Priority: 0,
|
||||||
|
IssueType: types.TypeFeature,
|
||||||
|
Status: types.StatusOpen,
|
||||||
|
},
|
||||||
|
labels: []string{"urgent"},
|
||||||
|
want: "[urgent]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "closed issue",
|
||||||
|
issue: &types.Issue{
|
||||||
|
ID: "test-def",
|
||||||
|
Title: "Closed Issue",
|
||||||
|
Priority: 3,
|
||||||
|
IssueType: types.TypeTask,
|
||||||
|
Status: types.StatusClosed,
|
||||||
|
},
|
||||||
|
labels: nil,
|
||||||
|
want: "Closed Issue",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var buf strings.Builder
|
||||||
|
formatIssueCompact(&buf, tt.issue, tt.labels)
|
||||||
|
result := buf.String()
|
||||||
|
if !strings.Contains(result, tt.want) {
|
||||||
|
t.Errorf("formatIssueCompact() = %q, want to contain %q", result, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseTimeFlag(t *testing.T) {
|
func TestParseTimeFlag(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
214
cmd/bd/utils_unit_test.go
Normal file
214
cmd/bd/utils_unit_test.go
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTruncateString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
maxLen int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"no truncation needed", "hello", 10, "hello"},
|
||||||
|
{"exact length", "hello", 5, "hello"},
|
||||||
|
{"truncate with ellipsis", "hello world", 8, "hello..."},
|
||||||
|
{"very short max", "hello", 3, "hel"},
|
||||||
|
{"max of 4", "hello world", 4, "h..."},
|
||||||
|
{"empty string", "", 5, ""},
|
||||||
|
// Note: truncateString operates on bytes, not runes
|
||||||
|
{"unicode", "hello\u4e16\u754c", 15, "hello\u4e16\u754c"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := truncateString(tt.input, tt.maxLen)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("truncateString(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluralize(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
count int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "s"},
|
||||||
|
{1, ""},
|
||||||
|
{2, "s"},
|
||||||
|
{100, "s"},
|
||||||
|
{-1, "s"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
got := pluralize(tt.count)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("pluralize(%d) = %q, want %q", tt.count, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatTimeAgo(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
t time.Time
|
||||||
|
wantContains string
|
||||||
|
}{
|
||||||
|
{"just now", now.Add(-30 * time.Second), "just now"},
|
||||||
|
{"1 minute ago", now.Add(-1 * time.Minute), "1 min ago"},
|
||||||
|
{"5 minutes ago", now.Add(-5 * time.Minute), "5 mins ago"},
|
||||||
|
{"1 hour ago", now.Add(-1 * time.Hour), "1 hour ago"},
|
||||||
|
{"3 hours ago", now.Add(-3 * time.Hour), "3 hours ago"},
|
||||||
|
{"1 day ago", now.Add(-24 * time.Hour), "1 day ago"},
|
||||||
|
{"3 days ago", now.Add(-72 * time.Hour), "3 days ago"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := formatTimeAgo(tt.t)
|
||||||
|
if got != tt.wantContains {
|
||||||
|
t.Errorf("formatTimeAgo() = %q, want %q", got, tt.wantContains)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainsLabel(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
labels []string
|
||||||
|
label string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"empty labels", []string{}, "bug", false},
|
||||||
|
{"single match", []string{"bug"}, "bug", true},
|
||||||
|
{"no match", []string{"feature", "enhancement"}, "bug", false},
|
||||||
|
{"match in list", []string{"bug", "feature", "urgent"}, "feature", true},
|
||||||
|
{"case sensitive", []string{"Bug"}, "bug", false},
|
||||||
|
{"nil labels", nil, "bug", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := containsLabel(tt.labels, tt.label)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("containsLabel(%v, %q) = %v, want %v", tt.labels, tt.label, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetContributorsSorted(t *testing.T) {
|
||||||
|
// Test that contributors are returned in sorted order by commit count
|
||||||
|
contributors := getContributorsSorted()
|
||||||
|
|
||||||
|
if len(contributors) == 0 {
|
||||||
|
t.Skip("No contributors defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we have at least some contributors
|
||||||
|
if len(contributors) < 1 {
|
||||||
|
t.Error("Expected at least one contributor")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify first contributor has most commits (descending order)
|
||||||
|
// We can't easily check counts, but we can verify the result is non-empty strings
|
||||||
|
for i, c := range contributors {
|
||||||
|
if c == "" {
|
||||||
|
t.Errorf("Contributor at index %d is empty string", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractIDSuffix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"hierarchical ID", "bd-123.1.2", "2"},
|
||||||
|
{"prefix-hash ID", "bd-abc123", "abc123"},
|
||||||
|
{"simple ID", "123", "123"},
|
||||||
|
{"multi-dot hierarchical", "prefix-xyz.1.2.3", "3"},
|
||||||
|
{"dot only", "a.b", "b"},
|
||||||
|
{"dash only", "a-b", "b"},
|
||||||
|
{"empty", "", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := extractIDSuffix(tt.id)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("extractIDSuffix(%q) = %q, want %q", tt.id, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
maxLen int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"no truncation", "short", 10, "short"},
|
||||||
|
{"exact length", "exact", 5, "exact"},
|
||||||
|
{"truncate needed", "long string here", 10, "long st..."},
|
||||||
|
{"very short max", "hello world", 5, "he..."},
|
||||||
|
{"empty string", "", 5, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := truncate(tt.s, tt.maxLen)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("truncate(%q, %d) = %q, want %q", tt.s, tt.maxLen, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncateDescription(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
desc string
|
||||||
|
maxLen int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"no truncation", "short", 10, "short"},
|
||||||
|
{"multiline takes first", "first line\nsecond line", 20, "first line"},
|
||||||
|
{"truncate with ellipsis", "a very long description here", 15, "a very long ..."},
|
||||||
|
{"multiline and truncate", "first line is long\nsecond", 10, "first l..."},
|
||||||
|
{"empty", "", 10, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := truncateDescription(tt.desc, tt.maxLen)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("truncateDescription(%q, %d) = %q, want %q", tt.desc, tt.maxLen, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test showCleanupDeprecationHint - just ensure it doesn't panic
|
||||||
|
func TestShowCleanupDeprecationHint(t *testing.T) {
|
||||||
|
// This function just prints to stdout, so we just verify it doesn't panic
|
||||||
|
showCleanupDeprecationHint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clearAutoFlushState - ensure it doesn't panic when called without initialization
|
||||||
|
func TestClearAutoFlushState(t *testing.T) {
|
||||||
|
// This should not panic even if flush manager isn't initialized
|
||||||
|
clearAutoFlushState()
|
||||||
|
}
|
||||||
@@ -134,6 +134,10 @@ func TestTrackBdVersion_FirstRun(t *testing.T) {
|
|||||||
t.Fatalf("Failed to create db file: %v", err)
|
t.Fatalf("Failed to create db file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set BEADS_DIR to force FindBeadsDir to use our temp directory
|
||||||
|
// This prevents finding the actual .beads in a git worktree
|
||||||
|
t.Setenv("BEADS_DIR", beadsDir)
|
||||||
|
|
||||||
// Change to temp directory
|
// Change to temp directory
|
||||||
t.Chdir(tmpDir)
|
t.Chdir(tmpDir)
|
||||||
|
|
||||||
@@ -176,6 +180,10 @@ func TestTrackBdVersion_UpgradeDetection(t *testing.T) {
|
|||||||
t.Fatalf("Failed to create .beads: %v", err)
|
t.Fatalf("Failed to create .beads: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set BEADS_DIR to force FindBeadsDir to use our temp directory
|
||||||
|
// This prevents finding the actual .beads in a git worktree
|
||||||
|
t.Setenv("BEADS_DIR", beadsDir)
|
||||||
|
|
||||||
// Change to temp directory
|
// Change to temp directory
|
||||||
t.Chdir(tmpDir)
|
t.Chdir(tmpDir)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user