@@ -63,6 +63,130 @@ class PreviewActionIntegrationTests: XCTestCase {
63
63
return ( sourceURL: sourceURL, outputURL: outputURL, templateURL: templateURL)
64
64
}
65
65
66
+ func testWatchRecoversAfterConversionErrors( ) async throws {
67
+ #if os(macOS)
68
+ let ( sourceURL, outputURL, templateURL) = try createPreviewSetup ( source: createMinimalDocsBundle ( ) )
69
+ defer {
70
+ try ? FileManager . default. removeItem ( at: sourceURL)
71
+ try ? FileManager . default. removeItem ( at: outputURL)
72
+ try ? FileManager . default. removeItem ( at: templateURL)
73
+ }
74
+
75
+ // A FileHandle to read action's output.
76
+ let pipeURL = try createTemporaryDirectory ( ) . appendingPathComponent ( " pipe " )
77
+ try Data ( ) . write ( to: pipeURL)
78
+ let fileHandle = try FileHandle ( forUpdating: pipeURL)
79
+ defer { fileHandle. closeFile ( ) }
80
+
81
+ let convertActionTempDirectory = try createTemporaryDirectory ( )
82
+ let createConvertAction = {
83
+ try ConvertAction (
84
+ documentationBundleURL: sourceURL,
85
+ outOfProcessResolver: nil ,
86
+ analyze: false ,
87
+ targetDirectory: outputURL,
88
+ htmlTemplateDirectory: templateURL,
89
+ emitDigest: false ,
90
+ currentPlatforms: nil ,
91
+ fileManager: FileManager . default,
92
+ temporaryDirectory: convertActionTempDirectory)
93
+ }
94
+
95
+ let preview = try PreviewAction (
96
+ port: 8080 , // We ignore this value when we set the `bindServerToSocketPath` property below.
97
+ createConvertAction: createConvertAction
98
+ )
99
+ defer {
100
+ try ? preview. stop ( )
101
+ }
102
+
103
+ preview. bindServerToSocketPath = try createTemporaryTestSocketPath ( )
104
+
105
+ let logStorage = LogHandle . LogStorage ( )
106
+
107
+ // The technology output file URL
108
+ let convertedOverviewURL = outputURL
109
+ . appendingPathComponent ( " data " )
110
+ . appendingPathComponent ( " tutorials " )
111
+ . appendingPathComponent ( " Overview.json " )
112
+
113
+ // Start watching the source and get the initial (successful) state.
114
+ do {
115
+ let didStartServerExpectation = asyncLogExpectation ( log: logStorage, description: " Did start the preview server " , expectedText: " ======= " )
116
+
117
+ // Start the preview and keep it running for the asserts that follow inside this test.
118
+ Task {
119
+ var logHandle = LogHandle . memory ( logStorage)
120
+ let result = try await preview. perform ( logHandle: & logHandle)
121
+
122
+ guard !result. problems. containsErrors else {
123
+ throw ErrorsEncountered ( )
124
+ }
125
+ }
126
+
127
+ // This should only take 1.5 seconds (1 second for the directory monitor debounce and 0.5 seconds for the expectation poll interval)
128
+ await fulfillment ( of: [ didStartServerExpectation] , timeout: 20.0 )
129
+
130
+ // Check the log output to confirm that expected informational text is printed
131
+ let logOutput = logStorage. text
132
+
133
+ let expectedLogIntroductoryOutput = """
134
+ Input: \( sourceURL. path)
135
+ Template: \( templateURL. path)
136
+ """
137
+ XCTAssertTrue ( logOutput. hasPrefix ( expectedLogIntroductoryOutput) , """
138
+ Missing expected input and template information in log/print output
139
+ """ )
140
+
141
+ if let previewInfoStart = logOutput. range ( of: " ===== \n " ) ? . upperBound,
142
+ let previewInfoEnd = logOutput [ previewInfoStart... ] . range ( of: " \n ===== " ) ? . lowerBound {
143
+ XCTAssertEqual ( logOutput [ previewInfoStart..< previewInfoEnd] , """
144
+ Starting Local Preview Server
145
+ \t Address: http://localhost:8080/documentation/mykit
146
+ \t http://localhost:8080/tutorials/overview
147
+ """ )
148
+ } else {
149
+ XCTFail ( " Missing preview information in log/print output " )
150
+ }
151
+
152
+ XCTAssertTrue ( FileManager . default. fileExists ( atPath: convertedOverviewURL. path, isDirectory: nil ) )
153
+ }
154
+
155
+ // Verify conversion result.
156
+ let overview = try JSONDecoder ( ) . decode ( RenderNode . self, from: Data ( contentsOf: convertedOverviewURL) )
157
+ let introSection = try XCTUnwrap ( overview. sections. first ( where: { $0. kind == . hero } ) as? IntroRenderSection )
158
+ XCTAssertEqual ( introSection. title, " Technology X " )
159
+
160
+ let invalidJSONSymbolGraphURL = sourceURL. appendingPathComponent ( " invalid-incomplete-data.symbols.json " )
161
+
162
+ // Start watching the source and detect failed conversion.
163
+ do {
164
+ let didFailRebuiltExpectation = asyncLogExpectation ( log: logStorage, description: " Did notice changed input and failed rebuild " , expectedText: " Compilation failed " )
165
+
166
+ // this is invalid JSON and will result in an error
167
+ try " { " . write ( to: invalidJSONSymbolGraphURL, atomically: true , encoding: . utf8)
168
+
169
+ // This should only take 1.5 seconds (1 second for the directory monitor debounce and 0.5 seconds for the expectation poll interval)
170
+ await fulfillment ( of: [ didFailRebuiltExpectation] , timeout: 20.0 )
171
+ }
172
+
173
+ // Start watching the source and detect recovery and successful conversion after a failure.
174
+ do {
175
+ let didSuccessfullyRebuiltExpectation = asyncLogExpectation ( log: logStorage, description: " Did notice changed input (again) and finished rebuild " , expectedText: " Done " )
176
+
177
+ try FileManager . default. removeItem ( at: invalidJSONSymbolGraphURL)
178
+
179
+ // This should only take 1.5 seconds (1 second for the directory monitor debounce and 0.5 seconds for the expectation poll interval)
180
+ await fulfillment ( of: [ didSuccessfullyRebuiltExpectation] , timeout: 20.0 )
181
+
182
+ // Check conversion result.
183
+ let overview = try JSONDecoder ( ) . decode ( RenderNode . self, from: Data ( contentsOf: convertedOverviewURL) )
184
+ let introSection = try XCTUnwrap ( overview. sections. first ( where: { $0. kind == . hero } ) as? IntroRenderSection )
185
+ XCTAssertEqual ( introSection. title, " Technology X " )
186
+ }
187
+ #endif
188
+ }
189
+
66
190
func testThrowsHumanFriendlyErrorWhenCannotStartServerOnAGivenPort( ) async throws {
67
191
// Binding an invalid address
68
192
try await assert ( bindPort: - 1 , expectedErrorMessage: " Can't start the preview server on port -1 " )
0 commit comments