Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8067574

Browse files
chore: add file sync daemon tests (#129)
These are just regression tests for the core file sync daemon functionality.Also has sync sessions ignore VCS directories by default, as per the file sync RFC.
1 parentde604d7 commit8067574

File tree

11 files changed

+274
-65
lines changed

11 files changed

+274
-65
lines changed

‎Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
5151
#elseif arch(x86_64)
5252
letmutagenBinary="mutagen-darwin-amd64"
5353
#endif
54-
fileSyncDaemon=MutagenDaemon(
54+
letfileSyncDaemon=MutagenDaemon(
5555
mutagenPath:Bundle.main.url(forResource: mutagenBinary, withExtension:nil)
5656
)
57+
Task{
58+
await fileSyncDaemon.tryStart()
59+
}
60+
self.fileSyncDaemon= fileSyncDaemon
5761
}
5862

5963
func applicationDidFinishLaunching(_:Notification){

‎Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ final class PreviewFileSync: FileSyncDaemon {
2020
state=.stopped
2121
}
2222

23-
func createSession(localPath _:String, agentHost _:String, remotePath _:String)asyncthrows(DaemonError){}
23+
func createSession(arg _:CreateSyncSessionRequest)asyncthrows(DaemonError){}
2424

2525
func deleteSessions(ids _:[String])asyncthrows(VPNLib.DaemonError){}
2626

‎Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,6 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
166166
defer{ loading=false}
167167
dothrows(DaemonError){
168168
tryawait fileSync.deleteSessions(ids:[selection!])
169-
if fileSync.sessionState.isEmpty{
170-
// Last session was deleted, stop the daemon
171-
await fileSync.stop()
172-
}
173169
} catch{
174170
actionError= error
175171
}

‎Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
100100
tryawait fileSync.deleteSessions(ids:[existingSession.id])
101101
}
102102
tryawait fileSync.createSession(
103-
localPath: localPath,
104-
agentHost: remoteHostname,
105-
remotePath: remotePath
103+
arg:.init(
104+
alpha:.init(path: localPath, protocolKind:.local),
105+
beta:.init(path: remotePath, protocolKind:.ssh(host: remoteHostname))
106+
)
106107
)
107108
} catch{
108109
createError= error

‎Coder-Desktop/Coder-DesktopTests/FilePickerTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ struct FilePickerTests {
103103
try disclosureGroup.expand()
104104

105105
// Disclosure group should expand out to 3 more directories
106-
try#expect(awaiteventually{@MainActorin
107-
returntryview.findAll(ViewType.DisclosureGroup.self).count==6
106+
#expect(awaiteventually{@MainActorin
107+
return view.findAll(ViewType.DisclosureGroup.self).count==6
108108
})
109109
}
110110
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
@testableimport Coder_Desktop
2+
import Foundation
3+
import GRPC
4+
import NIO
5+
import Subprocess
6+
import Testing
7+
import VPNLib
8+
import XCTest
9+
10+
@MainActor
11+
@Suite(.timeLimit(.minutes(1)))
12+
classFileSyncDaemonTests{
13+
lettempDir:URL
14+
letmutagenBinary:URL
15+
letmutagenDataDirectory:URL
16+
letmutagenAlphaDirectory:URL
17+
letmutagenBetaDirectory:URL
18+
19+
// Before each test
20+
init()throws{
21+
tempDir=FileManager.default.makeTempDir()!
22+
#if arch(arm64)
23+
letbinaryName="mutagen-darwin-arm64"
24+
#elseif arch(x86_64)
25+
letbinaryName="mutagen-darwin-amd64"
26+
#endif
27+
mutagenBinary=Bundle.main.url(forResource: binaryName, withExtension:nil)!
28+
mutagenDataDirectory= tempDir.appending(path:"mutagen")
29+
mutagenAlphaDirectory= tempDir.appending(path:"alpha")
30+
tryFileManager.default.createDirectory(at: mutagenAlphaDirectory, withIntermediateDirectories:true)
31+
mutagenBetaDirectory= tempDir.appending(path:"beta")
32+
tryFileManager.default.createDirectory(at: mutagenBetaDirectory, withIntermediateDirectories:true)
33+
}
34+
35+
// After each test
36+
deinit{
37+
try?FileManager.default.removeItem(at: tempDir)
38+
}
39+
40+
privatefunc statesEqual(_ first:DaemonState, _ second:DaemonState)->Bool{
41+
switch(first, second){
42+
case(.stopped,.stopped):
43+
true
44+
case(.running,.running):
45+
true
46+
case(.unavailable,.unavailable):
47+
true
48+
default:
49+
false
50+
}
51+
}
52+
53+
@Test
54+
func fullSync()asyncthrows{
55+
letdaemon=MutagenDaemon(mutagenPath: mutagenBinary, mutagenDataDirectory: mutagenDataDirectory)
56+
#expect(statesEqual(daemon.state,.stopped))
57+
#expect(daemon.sessionState.count==0)
58+
59+
// The daemon won't start until we create a session
60+
await daemon.tryStart()
61+
#expect(statesEqual(daemon.state,.stopped))
62+
#expect(daemon.sessionState.count==0)
63+
64+
tryawait daemon.createSession(
65+
arg:.init(
66+
alpha:.init(
67+
path: mutagenAlphaDirectory.path(),
68+
protocolKind:.local
69+
),
70+
beta:.init(
71+
path: mutagenBetaDirectory.path(),
72+
protocolKind:.local
73+
)
74+
)
75+
)
76+
77+
// Daemon should have started itself
78+
#expect(statesEqual(daemon.state,.running))
79+
#expect(daemon.sessionState.count==1)
80+
81+
// Write a file to Alpha
82+
letalphaFile= mutagenAlphaDirectory.appendingPathComponent("test.txt")
83+
try"Hello, World!".write(to: alphaFile, atomically:true, encoding:.utf8)
84+
#expect(
85+
awaiteventually(timeout:.seconds(5), interval:.milliseconds(100)){@MainActorin
86+
returnFileManager.default.fileExists(
87+
atPath:self.mutagenBetaDirectory.appending(path:"test.txt").path()
88+
)
89+
})
90+
91+
tryawait daemon.deleteSessions(ids: daemon.sessionState.map(\.id))
92+
#expect(daemon.sessionState.count==0)
93+
// Daemon should have stopped itself once all sessions are deleted
94+
#expect(statesEqual(daemon.state,.stopped))
95+
}
96+
97+
@Test
98+
func autoStopStart()asyncthrows{
99+
letdaemon=MutagenDaemon(mutagenPath: mutagenBinary, mutagenDataDirectory: mutagenDataDirectory)
100+
#expect(statesEqual(daemon.state,.stopped))
101+
#expect(daemon.sessionState.count==0)
102+
103+
tryawait daemon.createSession(
104+
arg:.init(
105+
alpha:.init(
106+
path: mutagenAlphaDirectory.path(),
107+
protocolKind:.local
108+
),
109+
beta:.init(
110+
path: mutagenBetaDirectory.path(),
111+
protocolKind:.local
112+
)
113+
)
114+
)
115+
116+
tryawait daemon.createSession(
117+
arg:.init(
118+
alpha:.init(
119+
path: mutagenAlphaDirectory.path(),
120+
protocolKind:.local
121+
),
122+
beta:.init(
123+
path: mutagenBetaDirectory.path(),
124+
protocolKind:.local
125+
)
126+
)
127+
)
128+
129+
#expect(statesEqual(daemon.state,.running))
130+
#expect(daemon.sessionState.count==2)
131+
132+
tryawait daemon.deleteSessions(ids:[daemon.sessionState[0].id])
133+
#expect(daemon.sessionState.count==1)
134+
#expect(statesEqual(daemon.state,.running))
135+
136+
tryawait daemon.deleteSessions(ids:[daemon.sessionState[0].id])
137+
#expect(daemon.sessionState.count==0)
138+
#expect(statesEqual(daemon.state,.stopped))
139+
}
140+
141+
@Test
142+
func orphaned()asyncthrows{
143+
letdaemon1=MutagenDaemon(mutagenPath: mutagenBinary, mutagenDataDirectory: mutagenDataDirectory)
144+
await daemon1.refreshSessions()
145+
tryawait daemon1.createSession(arg:
146+
.init(
147+
alpha:.init(
148+
path: mutagenAlphaDirectory.path(),
149+
protocolKind:.local
150+
),
151+
beta:.init(
152+
path: mutagenBetaDirectory.path(),
153+
protocolKind:.local
154+
)
155+
)
156+
)
157+
#expect(statesEqual(daemon1.state,.running))
158+
#expect(daemon1.sessionState.count==1)
159+
160+
letdaemon2=MutagenDaemon(mutagenPath: mutagenBinary, mutagenDataDirectory: mutagenDataDirectory)
161+
await daemon2.tryStart()
162+
#expect(statesEqual(daemon2.state,.running))
163+
164+
// Daemon 2 should have killed daemon 1, causing it to fail
165+
#expect(daemon1.state.isFailed)
166+
}
167+
}

‎Coder-Desktop/Coder-DesktopTests/Util.swift

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class MockFileSyncDaemon: FileSyncDaemon {
4747
[]
4848
}
4949

50-
func createSession(localPath _:String, agentHost _:String, remotePath _:String)asyncthrows(DaemonError){}
50+
func createSession(arg _:CreateSyncSessionRequest)asyncthrows(DaemonError){}
5151

5252
func pauseSessions(ids _:[String])asyncthrows(VPNLib.DaemonError){}
5353

@@ -61,24 +61,32 @@ extension Inspection: @unchecked Sendable, @retroactive InspectionEmissary {}
6161
publicfunc eventually(
6262
timeout:Duration=.milliseconds(500),
6363
interval:Duration=.milliseconds(10),
64-
condition:@escaping()asyncthrows->Bool
65-
)asyncthrows->Bool{
64+
condition:@Sendable()asyncthrows->Bool
65+
)asyncrethrows->Bool{
6666
letendTime=ContinuousClock.now.advanced(by: timeout)
6767

68-
varlastError:Error?
69-
7068
whileContinuousClock.now< endTime{
7169
do{
7270
iftryawaitcondition(){returntrue}
73-
lastError=nil
7471
}catch{
75-
lastError= error
7672
tryawaitTask.sleep(for: interval)
7773
}
7874
}
7975

80-
iflet lastError{
81-
throw lastError
76+
returntryawaitcondition()
77+
}
78+
79+
extensionFileManager{
80+
func makeTempDir()->URL?{
81+
lettempDirectory=FileManager.default.temporaryDirectory
82+
letdirectoryName=String(Int.random(in:0..<1_000_000))
83+
letdirectoryURL= tempDirectory.appendingPathComponent(directoryName)
84+
85+
do{
86+
tryFileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories:true)
87+
return directoryURL
88+
}catch{
89+
returnnil
90+
}
8291
}
83-
returnfalse
8492
}

‎Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public protocol FileSyncDaemon: ObservableObject {
1414
func tryStart()async
1515
func stop()async
1616
func refreshSessions()async
17-
func createSession(localPath:String, agentHost:String, remotePath:String)asyncthrows(DaemonError)
17+
func createSession(arg:CreateSyncSessionRequest)asyncthrows(DaemonError)
1818
func deleteSessions(ids:[String])asyncthrows(DaemonError)
1919
func pauseSessions(ids:[String])asyncthrows(DaemonError)
2020
func resumeSessions(ids:[String])asyncthrows(DaemonError)
@@ -76,21 +76,6 @@ public class MutagenDaemon: FileSyncDaemon {
7676
state=.unavailable
7777
return
7878
}
79-
80-
// If there are sync sessions, the daemon should be running
81-
Task{
82-
dothrows(DaemonError){
83-
tryawaitstart()
84-
} catch{
85-
state=.failed(error)
86-
return
87-
}
88-
awaitrefreshSessions()
89-
if sessionState.isEmpty{
90-
logger.info("No sync sessions found on startup, stopping daemon")
91-
awaitstop()
92-
}
93-
}
9479
}
9580

9681
publicfunc tryStart()async{
@@ -99,6 +84,12 @@ public class MutagenDaemon: FileSyncDaemon {
9984
tryawaitstart()
10085
} catch{
10186
state=.failed(error)
87+
return
88+
}
89+
awaitrefreshSessions()
90+
if sessionState.isEmpty{
91+
logger.info("No sync sessions found on startup, stopping daemon")
92+
awaitstop()
10293
}
10394
}
10495

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp