Skip to content

Commit 2c2be2b

Browse files
committed
added tests for ReadWriteFileLock
1 parent 1c8b72d commit 2c2be2b

File tree

3 files changed

+245
-0
lines changed

3 files changed

+245
-0
lines changed

Sources/TSCBasic/Thread.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ final public class Thread {
6464
}
6565
}
6666
}
67+
68+
/// Causes the calling thread to yield execution to another thread.
69+
public static func yield() {
70+
#if os(Windows)
71+
SwitchToThread()
72+
#else
73+
sched_yield()
74+
#endif
75+
}
6776
}
6877

6978
#if canImport(Darwin)

Tests/TSCBasicTests/FileSystemTests.swift

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,183 @@ class FileSystemTests: XCTestCase {
602602
}
603603
#endif
604604
}
605+
606+
func testInMemoryFileSystemFileLock() throws {
607+
let fs = InMemoryFileSystem()
608+
let path = AbsolutePath("/")
609+
try fs.createDirectory(path)
610+
611+
let fileA = path.appending(component: "fileA")
612+
let fileB = path.appending(component: "fileB")
613+
let lockFile = path.appending(component: "lockfile")
614+
615+
let writerThreads = (0..<100).map { _ in
616+
return Thread {
617+
try! fs.withLock(on: lockFile, type: .exclusive) {
618+
// Get thr current contents of the file if any.
619+
let valueA: Int
620+
if fs.exists(fileA) {
621+
valueA = Int(try fs.readFileContents(fileA).description) ?? 0
622+
} else {
623+
valueA = 0
624+
}
625+
// Sum and write back to file.
626+
try fs.writeFileContents(fileA, bytes: ByteString(encodingAsUTF8: String(valueA + 1)))
627+
628+
Thread.yield()
629+
630+
// Get thr current contents of the file if any.
631+
let valueB: Int
632+
if fs.exists(fileB) {
633+
valueB = Int(try fs.readFileContents(fileB).description) ?? 0
634+
} else {
635+
valueB = 0
636+
}
637+
// Sum and write back to file.
638+
try fs.writeFileContents(fileB, bytes: ByteString(encodingAsUTF8: String(valueB + 1)))
639+
}
640+
}
641+
}
642+
643+
let readerThreads = (0..<20).map { _ in
644+
return Thread {
645+
try! fs.withLock(on: lockFile, type: .shared) {
646+
try XCTAssertEqual(fs.readFileContents(fileA), fs.readFileContents(fileB))
647+
648+
Thread.yield()
649+
650+
try XCTAssertEqual(fs.readFileContents(fileA), fs.readFileContents(fileB))
651+
}
652+
}
653+
}
654+
655+
writerThreads.forEach { $0.start() }
656+
readerThreads.forEach { $0.start() }
657+
writerThreads.forEach { $0.join() }
658+
readerThreads.forEach { $0.join() }
659+
660+
try XCTAssertEqual(fs.readFileContents(fileA), "100")
661+
try XCTAssertEqual(fs.readFileContents(fileB), "100")
662+
}
663+
664+
func testLocalFileSystemFileLock() throws {
665+
try withTemporaryDirectory { tempDir in
666+
let fileA = tempDir.appending(component: "fileA")
667+
let fileB = tempDir.appending(component: "fileB")
668+
let lockFile = tempDir.appending(component: "lockfile")
669+
670+
let writerThreads = (0..<100).map { _ in
671+
return Thread {
672+
try! localFileSystem.withLock(on: lockFile, type: .exclusive) {
673+
// Get thr current contents of the file if any.
674+
let valueA: Int
675+
if localFileSystem.exists(fileA) {
676+
valueA = Int(try localFileSystem.readFileContents(fileA).description) ?? 0
677+
} else {
678+
valueA = 0
679+
}
680+
// Sum and write back to file.
681+
try localFileSystem.writeFileContents(fileA, bytes: ByteString(encodingAsUTF8: String(valueA + 1)))
682+
683+
Thread.yield()
684+
685+
// Get thr current contents of the file if any.
686+
let valueB: Int
687+
if localFileSystem.exists(fileB) {
688+
valueB = Int(try localFileSystem.readFileContents(fileB).description) ?? 0
689+
} else {
690+
valueB = 0
691+
}
692+
// Sum and write back to file.
693+
try localFileSystem.writeFileContents(fileB, bytes: ByteString(encodingAsUTF8: String(valueB + 1)))
694+
}
695+
}
696+
}
697+
698+
let readerThreads = (0..<20).map { _ in
699+
return Thread {
700+
try! localFileSystem.withLock(on: lockFile, type: .shared) {
701+
try XCTAssertEqual(localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
702+
703+
Thread.yield()
704+
705+
try XCTAssertEqual(localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
706+
}
707+
}
708+
}
709+
710+
writerThreads.forEach { $0.start() }
711+
readerThreads.forEach { $0.start() }
712+
writerThreads.forEach { $0.join() }
713+
readerThreads.forEach { $0.join() }
714+
715+
try XCTAssertEqual(localFileSystem.readFileContents(fileA), "100")
716+
try XCTAssertEqual(localFileSystem.readFileContents(fileB), "100")
717+
}
718+
}
719+
720+
func testRerootedFileSystemViewFileLock() throws {
721+
let inMemoryFS = InMemoryFileSystem()
722+
let rootPath = AbsolutePath("/tmp")
723+
try inMemoryFS.createDirectory(rootPath)
724+
725+
let fs = RerootedFileSystemView(inMemoryFS, rootedAt: rootPath)
726+
let path = AbsolutePath("/")
727+
try fs.createDirectory(path)
728+
729+
let fileA = path.appending(component: "fileA")
730+
let fileB = path.appending(component: "fileB")
731+
let lockFile = path.appending(component: "lockfile")
732+
733+
let writerThreads = (0..<100).map { _ in
734+
return Thread {
735+
try! fs.withLock(on: lockFile, type: .exclusive) {
736+
// Get thr current contents of the file if any.
737+
let valueA: Int
738+
if fs.exists(fileA) {
739+
valueA = Int(try! fs.readFileContents(fileA).description) ?? 0
740+
} else {
741+
valueA = 0
742+
}
743+
// Sum and write back to file.
744+
try! fs.writeFileContents(fileA, bytes: ByteString(encodingAsUTF8: String(valueA + 1)))
745+
746+
Thread.yield()
747+
748+
// Get thr current contents of the file if any.
749+
let valueB: Int
750+
if fs.exists(fileB) {
751+
valueB = Int(try fs.readFileContents(fileB).description) ?? 0
752+
} else {
753+
valueB = 0
754+
}
755+
// Sum and write back to file.
756+
try fs.writeFileContents(fileB, bytes: ByteString(encodingAsUTF8: String(valueB + 1)))
757+
}
758+
}
759+
}
760+
761+
let readerThreads = (0..<20).map { _ in
762+
return Thread {
763+
try! fs.withLock(on: lockFile, type: .shared) {
764+
try XCTAssertEqual(fs.readFileContents(fileA), fs.readFileContents(fileB))
765+
766+
Thread.yield()
767+
768+
try XCTAssertEqual(fs.readFileContents(fileA), fs.readFileContents(fileB))
769+
}
770+
}
771+
}
772+
773+
writerThreads.forEach { $0.start() }
774+
readerThreads.forEach { $0.start() }
775+
writerThreads.forEach { $0.join() }
776+
readerThreads.forEach { $0.join() }
777+
778+
try XCTAssertEqual(fs.readFileContents(fileA), "100")
779+
try XCTAssertEqual(fs.readFileContents(fileB), "100")
780+
}
781+
605782
}
606783

607784
/// Helper method to test file tree removal method on the given file system.

Tests/TSCBasicTests/LockTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,63 @@ class LockTests: XCTestCase {
8181
readerThreads.forEach { $0.join() }
8282
}
8383

84+
func testReadWriteFileLock() throws {
85+
try withTemporaryDirectory { tempDir in
86+
let fileA = tempDir.appending(component: "fileA")
87+
let fileB = tempDir.appending(component: "fileB")
88+
89+
let lock = FileLock(name: "lockfile", cachePath: tempDir)
90+
91+
let writerThreads = (0..<100).map { _ in
92+
return Thread {
93+
let lock = FileLock(name: "foo", cachePath: tempDir)
94+
try! lock.withLock(type: .exclusive) {
95+
// Get thr current contents of the file if any.
96+
let valueA: Int
97+
if localFileSystem.exists(fileA) {
98+
valueA = Int(try localFileSystem.readFileContents(fileA).description) ?? 0
99+
} else {
100+
valueA = 0
101+
}
102+
// Sum and write back to file.
103+
try localFileSystem.writeFileContents(fileA, bytes: ByteString(encodingAsUTF8: String(valueA + 1)))
104+
105+
Thread.yield()
106+
107+
// Get thr current contents of the file if any.
108+
let valueB: Int
109+
if localFileSystem.exists(fileB) {
110+
valueB = Int(try localFileSystem.readFileContents(fileB).description) ?? 0
111+
} else {
112+
valueB = 0
113+
}
114+
// Sum and write back to file.
115+
try localFileSystem.writeFileContents(fileB, bytes: ByteString(encodingAsUTF8: String(valueB + 1)))
116+
}
117+
}
118+
}
119+
120+
let readerThreads = (0..<20).map { _ in
121+
return Thread {
122+
let lock = FileLock(name: "foo", cachePath: tempDir)
123+
try! lock.withLock(type: .shared) {
124+
try XCTAssertEqual( localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
125+
126+
Thread.yield()
127+
128+
try XCTAssertEqual( localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
129+
}
130+
}
131+
}
132+
133+
writerThreads.forEach { $0.start() }
134+
readerThreads.forEach { $0.start() }
135+
writerThreads.forEach { $0.join() }
136+
readerThreads.forEach { $0.join() }
137+
138+
try XCTAssertEqual(localFileSystem.readFileContents(fileA), "100")
139+
try XCTAssertEqual(localFileSystem.readFileContents(fileB), "100")
140+
}
141+
}
142+
84143
}

0 commit comments

Comments
 (0)