mirror of
https://github.com/apple/container.git
synced 2026-11-05 13:36:25 +00:00
- Closes #2374. - `container logs -f` exits when its initial read finds an empty log. Skip the empty initial read without returning, so follow mode can wait for new output. - Keep the full-file read to preserve existing output before following. - Add a regression test for output appended to an initially empty log. - Leave container machine logs unchanged because it has a separate implementation and this issue concerns container logs.
70 lines
3.0 KiB
Swift
70 lines
3.0 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2026 Apple Inc. and the container project authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Darwin
|
|
import Foundation
|
|
import Testing
|
|
|
|
@testable import ContainerCommands
|
|
|
|
struct ContainerLogsTests {
|
|
@Test
|
|
func followsInitiallyEmptyLog() async throws {
|
|
// An exit test isolates stdout and its buffering from the other unit tests.
|
|
try await #require(processExitsWith: .success) {
|
|
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
let logURL = directory.appendingPathComponent("container.log")
|
|
let outputURL = directory.appendingPathComponent("stdout.log")
|
|
try Data().write(to: logURL)
|
|
try Data().write(to: outputURL)
|
|
let reader = try FileHandle(forReadingFrom: logURL)
|
|
let writer = try FileHandle(forWritingTo: logURL)
|
|
let output = try FileHandle(forWritingTo: outputURL)
|
|
defer {
|
|
reader.readabilityHandler = nil
|
|
try? reader.close()
|
|
try? writer.close()
|
|
try? output.close()
|
|
}
|
|
try #require(dup2(output.fileDescriptor, STDOUT_FILENO) == STDOUT_FILENO)
|
|
|
|
let follower = Task {
|
|
try await Application.ContainerLogs.tail(fh: reader, n: nil, follow: true)
|
|
}
|
|
defer { follower.cancel() }
|
|
|
|
let deadline = ContinuousClock.now + .seconds(10)
|
|
while reader.readabilityHandler == nil && ContinuousClock.now < deadline {
|
|
try await Task.sleep(for: .milliseconds(10))
|
|
}
|
|
try #require(reader.readabilityHandler != nil, "following an empty log must not return early")
|
|
|
|
let expected = Data("hello\n".utf8)
|
|
try writer.write(contentsOf: expected)
|
|
while try Data(contentsOf: outputURL) != expected && ContinuousClock.now < deadline {
|
|
try await Task.sleep(for: .milliseconds(10))
|
|
}
|
|
try #require(try Data(contentsOf: outputURL) == expected, "follow must print output appended to the empty log")
|
|
|
|
follower.cancel()
|
|
try await follower.value
|
|
}
|
|
}
|
|
}
|