4
4
* Licensed under the MIT license. See LICENSE file.
5
5
*/
6
6
7
+ import Logging
8
+ import TSCBasic
7
9
import XCTest
8
10
@testable import ShellOut
9
11
10
- func XCTAssertEqualAsync< T> (
11
- _ expression1: @autoclosure ( ) async throws -> T ,
12
- _ expression2: @autoclosure ( ) async throws -> T ,
13
- _ message: @autoclosure ( ) -> String = " " ,
14
- file: StaticString = #filePath,
15
- line: UInt = #line
16
- ) async where T: Equatable {
17
- do {
18
- let expr1 = try await expression1 ( )
19
- let expr2 = try await expression2 ( )
20
-
21
- return XCTAssertEqual ( expr1, expr2, message ( ) , file: file, line: line)
22
- } catch {
23
- // Trick XCTest into behaving correctly for a thrown error.
24
- return XCTAssertEqual ( try { ( ) -> Bool in throw error } ( ) , false , message ( ) , file: file, line: line)
12
+ final class ShellOutTests : XCTestCase {
13
+ static var temporaryDirectoryUrl : URL !
14
+ static var logger : Logger !
15
+
16
+ override class func setUp( ) {
17
+ XCTAssert ( isLoggingConfigured)
18
+ self . logger = . init( label: " test " )
19
+ // This is technically a misuse of the `withTemporaryDirectory()` utility, but there's no equivalent of
20
+ // the `TemporaryFile` API for drectories and this is (much) easier than directly invoking `mkdtemp(3)`.
21
+ // This is done in the class setUp rather than the instance method so it happens only once per test run,
22
+ // rather than for each single test.
23
+ self . temporaryDirectoryUrl = try ! withTemporaryDirectory ( prefix: " ShellOutTests " , removeTreeOnDeinit: false , { $0. asURL } )
25
24
}
26
- }
27
-
28
- class ShellOutTests : XCTestCase {
25
+
26
+ override class func tearDown( ) {
27
+ try ? FileManager . default. removeItem ( at: self . temporaryDirectoryUrl)
28
+ }
29
+
30
+ var logger : Logger { Self . logger }
31
+ var tempUrl : URL { Self . temporaryDirectoryUrl }
32
+ func tempUrl( filename: String ) -> URL { Self . temporaryDirectoryUrl. appendingPathComponent ( filename, isDirectory: false ) }
33
+ func tempUrl( directory: String ) -> URL { Self . temporaryDirectoryUrl. appendingPathComponent ( directory, isDirectory: true ) }
34
+
29
35
func test_appendArguments( ) throws {
30
36
var cmd = ShellOutCommand ( command: " foo " )
31
37
XCTAssertEqual ( cmd. description, " foo " )
@@ -50,26 +56,27 @@ class ShellOutTests: XCTestCase {
50
56
}
51
57
52
58
func testWithoutArguments( ) async throws {
53
- let uptime = try await shellOut ( to: " uptime " ) . stdout
59
+ let uptime = try await shellOut ( to: " uptime " , logger : logger ) . stdout
54
60
XCTAssertTrue ( uptime. contains ( " load average " ) )
55
61
}
56
62
57
63
func testWithArguments( ) async throws {
58
- let echo = try await shellOut ( to: " echo " , arguments: [ " Hello world " ] ) . stdout
64
+ let echo = try await shellOut ( to: " echo " , arguments: [ " Hello world " ] , logger : logger ) . stdout
59
65
XCTAssertEqual ( echo, " Hello world " )
60
66
}
61
67
62
68
func testSingleCommandAtPath( ) async throws {
63
- let tempDir = NSTemporaryDirectory ( )
64
69
try await shellOut (
65
70
to: " bash " ,
66
- arguments: [ " -c " , #"echo Hello > " \#( tempDir) /ShellOutTests-SingleCommand.txt""# ]
71
+ arguments: [ " -c " , #"echo Hello > " \#( tempUrl ( filename: " ShellOutTests-SingleCommand.txt " ) . path) ""# ] ,
72
+ logger: logger
67
73
)
68
74
69
75
let textFileContent = try await shellOut (
70
76
to: " cat " ,
71
77
arguments: [ " ShellOutTests-SingleCommand.txt " ] ,
72
- at: tempDir
78
+ at: tempUrl. path,
79
+ logger: logger
73
80
) . stdout
74
81
75
82
XCTAssertEqual ( textFileContent, " Hello " )
@@ -78,31 +85,33 @@ class ShellOutTests: XCTestCase {
78
85
func testSingleCommandAtPathContainingSpace( ) async throws {
79
86
try await shellOut ( to: " mkdir " ,
80
87
arguments: [ " -p " , " ShellOut Test Folder " ] ,
81
- at: NSTemporaryDirectory ( ) )
88
+ at: tempUrl. path,
89
+ logger: logger)
90
+ let testFolderUrl = tempUrl ( directory: " ShellOut Test Folder " )
82
91
try await shellOut ( to: " bash " , arguments: [ " -c " , " echo Hello > File " ] ,
83
- at: NSTemporaryDirectory ( ) + " ShellOut Test Folder " )
92
+ at: testFolderUrl. path,
93
+ logger: logger)
84
94
85
95
let output = try await shellOut (
86
96
to: " cat " ,
87
- arguments: [ " \( NSTemporaryDirectory ( ) ) ShellOut Test Folder/File " ] ) . stdout
97
+ arguments: [ testFolderUrl. appendingPathComponent ( " File " , isDirectory: false ) . path] ,
98
+ logger: logger) . stdout
88
99
XCTAssertEqual ( output, " Hello " )
89
100
}
90
101
91
102
func testSingleCommandAtPathContainingTilde( ) async throws {
92
- let homeContents = try await shellOut ( to: " ls " , arguments: [ " -a " ] , at: " ~ " ) . stdout
103
+ let homeContents = try await shellOut ( to: " ls " , arguments: [ " -a " ] , at: " ~ " , logger : logger ) . stdout
93
104
XCTAssertFalse ( homeContents. isEmpty)
94
105
}
95
106
96
107
func testThrowingError( ) async {
97
- do {
98
- try await shellOut ( to : . bash ( arguments : [ " cd notADirectory " ] ) )
99
- XCTFail ( " Expected expression to throw " )
100
- } catch let error as ShellOutError {
108
+ await XCTAssertThrowsErrorAsync ( try await shellOut ( to : . bash ( arguments : [ " cd notADirectory " ] ) , logger : logger ) ) {
109
+ guard let error = $0 as? ShellOutError else {
110
+ return XCTFail ( " Expected ShellOutError, got \( String ( reflecting : $0 ) ) " )
111
+ }
101
112
XCTAssertTrue ( error. message. contains ( " notADirectory " ) )
102
113
XCTAssertTrue ( error. output. isEmpty)
103
114
XCTAssertTrue ( error. terminationStatus != 0 )
104
- } catch {
105
- XCTFail ( " Invalid error type: \( error) " )
106
115
}
107
116
}
108
117
@@ -131,6 +140,7 @@ class ShellOutTests: XCTestCase {
131
140
let pipe = Pipe ( )
132
141
let output = try await shellOut ( to: " echo " ,
133
142
arguments: [ " Hello " ] ,
143
+ logger: logger,
134
144
outputHandle: pipe. fileHandleForWriting) . stdout
135
145
let capturedData = pipe. fileHandleForReading. readDataToEndOfFile ( )
136
146
XCTAssertEqual ( output, " Hello " )
@@ -140,72 +150,75 @@ class ShellOutTests: XCTestCase {
140
150
func testCapturingErrorWithHandle( ) async throws {
141
151
let pipe = Pipe ( )
142
152
143
- do {
144
- try await shellOut ( to: . bash( arguments: [ " cd notADirectory " ] ) ,
145
- errorHandle: pipe. fileHandleForWriting)
146
- XCTFail ( " Expected expression to throw " )
147
- } catch let error as ShellOutError {
153
+ await XCTAssertThrowsErrorAsync (
154
+ try await shellOut ( to: . bash( arguments: [ " cd notADirectory " ] ) , logger: logger, errorHandle: pipe. fileHandleForWriting)
155
+ ) {
156
+ guard let error = $0 as? ShellOutError else {
157
+ return XCTFail ( " Expected ShellOutError, got \( String ( reflecting: $0) ) " )
158
+ }
148
159
XCTAssertTrue ( error. message. contains ( " notADirectory " ) )
149
160
XCTAssertTrue ( error. output. isEmpty)
150
161
XCTAssertTrue ( error. terminationStatus != 0 )
151
162
152
163
let capturedData = pipe. fileHandleForReading. readDataToEndOfFile ( )
153
- XCTAssertEqual ( error. message + " \n " , String ( data: capturedData, encoding: . utf8) )
154
- } catch {
155
- XCTFail ( " Invalid error type: \( error) " )
164
+ XCTAssertEqual ( error. message + " \n " , String ( decoding: capturedData, as: UTF8 . self) )
156
165
}
157
166
}
158
167
159
168
func testGitCommands( ) async throws {
160
169
// Setup & clear state
161
- let tempFolderPath = NSTemporaryDirectory ( )
162
- try await shellOut ( to: " rm " ,
163
- arguments: [ " -rf " , " GitTestOrigin " ] ,
164
- at: tempFolderPath, logger: . init( label: " test " ) )
165
- try await shellOut ( to: " rm " ,
166
- arguments: [ " -rf " , " GitTestClone " ] ,
167
- at: tempFolderPath, logger: . init( label: " test " ) )
170
+ try await shellOut (
171
+ to: " rm " ,
172
+ arguments: [ " -rf " , " GitTestOrigin " ] ,
173
+ at: tempUrl. path,
174
+ logger: logger
175
+ )
176
+ try await shellOut (
177
+ to: " rm " ,
178
+ arguments: [ " -rf " , " GitTestClone " ] ,
179
+ at: tempUrl. path,
180
+ logger: logger
181
+ )
168
182
169
183
// Create a origin repository and make a commit with a file
170
- let originPath = tempFolderPath + " / GitTestOrigin"
171
- try await shellOut ( to: . createFolder( named: " GitTestOrigin " ) , at: tempFolderPath , logger: . init ( label : " test " ) )
172
- try await shellOut ( to: . gitInit( ) , at: originPath , logger: . init ( label : " test " ) )
173
- try await shellOut ( to: . createFile( named: " Test " , contents: " Hello world " ) , at: originPath , logger: . init ( label : " test " ) )
174
- try await shellOut ( to: " git " , arguments: [ " add " , " . " ] , at: originPath , logger: . init ( label : " test " ) )
175
- try await shellOut ( to: . gitCommit( message: " Commit " ) , at: originPath , logger: . init ( label : " test " ) )
184
+ let originDir = tempUrl ( directory : " GitTestOrigin " )
185
+ try await shellOut ( to: . createFolder( named: " GitTestOrigin " ) , at: tempUrl . path , logger: logger )
186
+ try await shellOut ( to: . gitInit( ) , at: originDir . path , logger: logger )
187
+ try await shellOut ( to: . createFile( named: " Test " , contents: " Hello world " ) , at: originDir . path , logger: logger )
188
+ try await shellOut ( to: " git " , arguments: [ " add " , " . " ] , at: originDir . path , logger: logger )
189
+ try await shellOut ( to: . gitCommit( message: " Commit " ) , at: originDir . path , logger: logger )
176
190
177
191
// Clone to a new repository and read the file
178
- let clonePath = tempFolderPath + " /GitTestClone "
179
- let cloneURL = URL ( fileURLWithPath: originPath)
180
- try await shellOut ( to: . gitClone( url: cloneURL, to: " GitTestClone " ) , at: tempFolderPath, logger: . init( label: " test " ) )
192
+ let cloneDir = tempUrl ( directory: " GitTestClone " )
193
+ try await shellOut ( to: . gitClone( url: originDir, to: " GitTestClone " ) , at: tempUrl. path, logger: logger)
181
194
182
- let filePath = clonePath + " / Test"
183
- await XCTAssertEqualAsync ( try await shellOut ( to: . readFile( at: filePath ) , logger: . init ( label : " test " ) ) . stdout, " Hello world " )
195
+ let fileUrl = cloneDir . appendingPathComponent ( " Test " , isDirectory : false )
196
+ await XCTAssertEqualAsync ( try await shellOut ( to: . readFile( at: fileUrl . path ) , logger: logger ) . stdout, " Hello world " )
184
197
185
198
// Make a new commit in the origin repository
186
- try await shellOut ( to: . createFile( named: " Test " , contents: " Hello again " ) , at: originPath , logger: . init ( label : " test " ) )
187
- try await shellOut ( to: . gitCommit( message: " Commit " ) , at: originPath , logger: . init ( label : " test " ) )
199
+ try await shellOut ( to: . createFile( named: " Test " , contents: " Hello again " ) , at: originDir . path , logger: logger )
200
+ try await shellOut ( to: . gitCommit( message: " Commit " ) , at: originDir . path , logger: logger )
188
201
189
202
// Pull the commit in the clone repository and read the file again
190
- try await shellOut ( to: . gitPull( ) , at: clonePath )
191
- await XCTAssertEqualAsync ( try await shellOut ( to: . readFile( at: filePath ) , logger: . init ( label : " test " ) ) . stdout, " Hello again " )
203
+ try await shellOut ( to: . gitPull( ) , at: cloneDir . path , logger : logger )
204
+ await XCTAssertEqualAsync ( try await shellOut ( to: . readFile( at: fileUrl . path ) , logger: logger ) . stdout, " Hello again " )
192
205
}
193
206
194
207
func testBash( ) async throws {
195
208
// Without explicit -c parameter
196
- await XCTAssertEqualAsync ( try await shellOut ( to: . bash( arguments: [ " echo " , " foo " ] ) ) . stdout,
209
+ await XCTAssertEqualAsync ( try await shellOut ( to: . bash( arguments: [ " echo " , " foo " ] ) , logger : logger ) . stdout,
197
210
" foo " )
198
211
// With explicit -c parameter
199
- await XCTAssertEqualAsync ( try await shellOut ( to: . bash( arguments: [ " -c " , " echo " , " foo " ] ) ) . stdout,
212
+ await XCTAssertEqualAsync ( try await shellOut ( to: . bash( arguments: [ " -c " , " echo " , " foo " ] ) , logger : logger ) . stdout,
200
213
" foo " )
201
214
}
202
215
203
216
func testBashArgumentQuoting( ) async throws {
204
217
await XCTAssertEqualAsync ( try await shellOut ( to: . bash( arguments: [ " echo " ,
205
- " foo ; echo bar " . quoted] ) ) . stdout,
218
+ " foo ; echo bar " . quoted] ) , logger : logger ) . stdout,
206
219
" foo ; echo bar " )
207
220
await XCTAssertEqualAsync ( try await shellOut ( to: . bash( arguments: [ " echo " ,
208
- " foo ; echo bar " . verbatim] ) ) . stdout,
221
+ " foo ; echo bar " . verbatim] ) , logger : logger ) . stdout,
209
222
" foo \n bar " )
210
223
}
211
224
@@ -221,47 +234,45 @@ class ShellOutTests: XCTestCase {
221
234
222
235
func test_git_tags( ) async throws {
223
236
// setup
224
- let tempDir = NSTemporaryDirectory ( ) . appending ( " test_stress_ \( UUID ( ) ) " )
225
- defer {
226
- try ? Foundation . FileManager. default. removeItem ( atPath: tempDir)
227
- }
237
+ let tempDir = tempUrl ( directory: " test_stress_ \( UUID ( ) ) " )
228
238
let sampleGitRepoName = " ErrNo "
229
- let sampleGitRepoZipFile = fixturesDirectory ( )
230
- . appendingPathComponent ( " \( sampleGitRepoName) .zip " ) . path
231
- let path = " \( tempDir ) / \( sampleGitRepoName ) "
232
- try ! Foundation . FileManager. default. createDirectory ( atPath : tempDir, withIntermediateDirectories: false , attributes: nil )
233
- try ! await ShellOut . shellOut ( to: . init( command: " unzip " , arguments: [ sampleGitRepoZipFile] ) , at: tempDir)
239
+ let sampleGitRepoZipFile = fixturesDirectory ( ) . appendingPathComponent ( sampleGitRepoName , isDirectory : false ) . appendingPathExtension ( " zip " ) . path
240
+ let sampleGitRepoDir = tempDir . appendingPathComponent ( sampleGitRepoName, isDirectory : true )
241
+
242
+ try Foundation . FileManager. default. createDirectory ( at : tempDir, withIntermediateDirectories: false , attributes: nil )
243
+ try await ShellOut . shellOut ( to: . init( command: " unzip " , arguments: [ sampleGitRepoZipFile] ) , at: tempDir. path , logger : logger )
234
244
235
245
// MUT
236
- await XCTAssertEqualAsync ( try await shellOut ( to: ShellOutCommand ( command: " git " , arguments: [ " tag " ] ) ,
237
- at: path) . stdout, """
238
- 0.2.0
239
- 0.2.1
240
- 0.2.2
241
- 0.2.3
242
- 0.2.4
243
- 0.2.5
244
- 0.3.0
245
- 0.4.0
246
- 0.4.1
247
- 0.4.2
248
- 0.5.0
249
- 0.5.1
250
- 0.5.2
251
- v0.0.1
252
- v0.0.2
253
- v0.0.3
254
- v0.0.4
255
- v0.0.5
256
- v0.1.0
257
- """ )
246
+ await XCTAssertEqualAsync (
247
+ try await shellOut ( to: ShellOutCommand ( command: " git " , arguments: [ " tag " ] ) , at: sampleGitRepoDir. path, logger: logger) . stdout,
248
+ """
249
+ 0.2.0
250
+ 0.2.1
251
+ 0.2.2
252
+ 0.2.3
253
+ 0.2.4
254
+ 0.2.5
255
+ 0.3.0
256
+ 0.4.0
257
+ 0.4.1
258
+ 0.4.2
259
+ 0.5.0
260
+ 0.5.1
261
+ 0.5.2
262
+ v0.0.1
263
+ v0.0.2
264
+ v0.0.3
265
+ v0.0.4
266
+ v0.0.5
267
+ v0.1.0
268
+ """ )
258
269
}
259
270
}
260
271
261
272
extension ShellOutTests {
262
- func fixturesDirectory( path: String = #file ) -> URL {
263
- let url = URL ( fileURLWithPath: path)
273
+ func fixturesDirectory( path: String = #filePath ) -> URL {
274
+ let url = URL ( fileURLWithPath: path, isDirectory : false )
264
275
let testsDir = url. deletingLastPathComponent ( )
265
- return testsDir. appendingPathComponent ( " Fixtures " )
276
+ return testsDir. appendingPathComponent ( " Fixtures " , isDirectory : true )
266
277
}
267
278
}
0 commit comments