Skip to content

Commit 78ed8d8

Browse files
committed
Added tests for shutdown and fixed a failing case
1 parent b924410 commit 78ed8d8

4 files changed

Lines changed: 145 additions & 0 deletions

File tree

‎cmd/lock_test.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func TestAcquireLock(t *testing.T) {
1414
// Setup isolation
1515
tempDir := t.TempDir()
1616
t.Setenv("XDG_CONFIG_HOME", tempDir)
17+
t.Setenv("APPDATA", tempDir)
1718

1819
// Ensure dirs exist (mocking what root.go does)
1920
err := config.EnsureDirs()

‎cmd/main_test.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ func TestMain(m *testing.M) {
99
tmpDir, err := os.MkdirTemp("", "surge-cmd-test-*")
1010
if err == nil {
1111
_ = os.Setenv("XDG_CONFIG_HOME", tmpDir)
12+
_ = os.Setenv("APPDATA", tmpDir)
1213
}
1314

1415
code := m.Run()

‎cmd/shutdown_test.go‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cmd
2+
3+
import (
4+
"sync"
5+
"sync/atomic"
6+
"testing"
7+
)
8+
9+
func TestExecuteGlobalShutdown_Once(t *testing.T) {
10+
var calls int32
11+
resetGlobalShutdownCoordinatorForTest(func() error {
12+
atomic.AddInt32(&calls, 1)
13+
return nil
14+
})
15+
t.Cleanup(func() { resetGlobalShutdownCoordinatorForTest(nil) })
16+
17+
var wg sync.WaitGroup
18+
for i := 0; i < 8; i++ {
19+
wg.Add(1)
20+
go func() {
21+
defer wg.Done()
22+
if err := executeGlobalShutdown("test"); err != nil {
23+
t.Errorf("executeGlobalShutdown returned error: %v", err)
24+
}
25+
}()
26+
}
27+
wg.Wait()
28+
29+
if got := atomic.LoadInt32(&calls); got != 1 {
30+
t.Fatalf("shutdown function calls = %d, want 1", got)
31+
}
32+
}

‎internal/core/local_service_test.go‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/surge-downloader/surge/internal/engine/events"
1212
"github.com/surge-downloader/surge/internal/engine/state"
1313
"github.com/surge-downloader/surge/internal/engine/types"
14+
"github.com/surge-downloader/surge/internal/testutil"
1415
)
1516

1617
func TestLocalDownloadService_Delete_DBOnlyBroadcastsRemoved(t *testing.T) {
@@ -81,3 +82,113 @@ func TestLocalDownloadService_Delete_DBOnlyBroadcastsRemoved(t *testing.T) {
8182
t.Fatalf("expected entry to be removed, got %+v", entry)
8283
}
8384
}
85+
86+
func TestLocalDownloadService_Shutdown_Idempotent(t *testing.T) {
87+
ch := make(chan interface{}, 1)
88+
svc := NewLocalDownloadServiceWithInput(nil, ch)
89+
90+
if err := svc.Shutdown(); err != nil {
91+
t.Fatalf("first shutdown failed: %v", err)
92+
}
93+
94+
select {
95+
case _, ok := <-ch:
96+
if ok {
97+
t.Fatal("expected input channel to be closed after shutdown")
98+
}
99+
case <-time.After(500 * time.Millisecond):
100+
t.Fatal("timed out waiting for input channel to close")
101+
}
102+
103+
if err := svc.Shutdown(); err != nil {
104+
t.Fatalf("second shutdown failed: %v", err)
105+
}
106+
}
107+
108+
func TestLocalDownloadService_Shutdown_PersistsPausedState(t *testing.T) {
109+
tempDir := t.TempDir()
110+
state.CloseDB()
111+
state.Configure(filepath.Join(tempDir, "surge.db"))
112+
defer state.CloseDB()
113+
114+
ch := make(chan interface{}, 100)
115+
pool := download.NewWorkerPool(ch, 1)
116+
svc := NewLocalDownloadServiceWithInput(pool, ch)
117+
defer func() { _ = svc.Shutdown() }()
118+
119+
server := testutil.NewStreamingMockServerT(t,
120+
500*1024*1024,
121+
testutil.WithRangeSupport(true),
122+
testutil.WithLatency(10*time.Millisecond),
123+
)
124+
defer server.Close()
125+
126+
outputDir := t.TempDir()
127+
const filename = "persist.bin"
128+
id, err := svc.Add(server.URL(), outputDir, filename, nil, nil)
129+
if err != nil {
130+
t.Fatalf("failed to add download: %v", err)
131+
}
132+
133+
deadline := time.Now().Add(8 * time.Second)
134+
progressed := false
135+
for time.Now().Before(deadline) {
136+
st, err := svc.GetStatus(id)
137+
if err == nil && st != nil && st.Downloaded > 0 {
138+
progressed = true
139+
break
140+
}
141+
time.Sleep(50 * time.Millisecond)
142+
}
143+
if !progressed {
144+
t.Fatal("download did not make progress before shutdown")
145+
}
146+
147+
if err := svc.Shutdown(); err != nil {
148+
t.Fatalf("shutdown failed: %v", err)
149+
}
150+
151+
entry, err := state.GetDownload(id)
152+
if err != nil {
153+
t.Fatalf("failed to fetch persisted download: %v", err)
154+
}
155+
if entry == nil {
156+
t.Fatal("expected persisted download entry after shutdown")
157+
}
158+
if entry.Status != "paused" {
159+
t.Fatalf("status = %q, want paused", entry.Status)
160+
}
161+
if entry.Downloaded == 0 {
162+
t.Fatal("expected persisted paused download to have non-zero progress")
163+
}
164+
165+
statuses, err := svc.List()
166+
if err != nil {
167+
t.Fatalf("failed to list downloads after shutdown: %v", err)
168+
}
169+
foundInList := false
170+
for _, st := range statuses {
171+
if st.ID == id {
172+
foundInList = true
173+
if st.Status != "paused" && st.Status != "pausing" {
174+
t.Fatalf("list status = %q, want paused/pausing", st.Status)
175+
}
176+
break
177+
}
178+
}
179+
if !foundInList {
180+
t.Fatal("expected paused download to remain visible in list after shutdown")
181+
}
182+
183+
destPath := filepath.Join(outputDir, filename)
184+
saved, err := state.LoadState(server.URL(), destPath)
185+
if err != nil {
186+
t.Fatalf("failed to load saved state: %v", err)
187+
}
188+
if saved.ID != id {
189+
t.Fatalf("saved state id = %q, want %q", saved.ID, id)
190+
}
191+
if len(saved.Tasks) == 0 {
192+
t.Fatal("expected saved state to include remaining tasks")
193+
}
194+
}

0 commit comments

Comments
 (0)