diff --git a/Sources/ContainerCommands/System/SystemLogs.swift b/Sources/ContainerCommands/System/SystemLogs.swift index b00008e9b..476010e2f 100644 --- a/Sources/ContainerCommands/System/SystemLogs.swift +++ b/Sources/ContainerCommands/System/SystemLogs.swift @@ -35,7 +35,7 @@ extension Application { @Option( name: .long, - help: "Fetch logs starting from the specified time period (minus the current time); supported formats: m, h, d" + help: "Fetch logs starting from the specified time period (minus the current time); supported formats: [m|h|d] (defaults to seconds)" ) var last: String = "5m" @@ -44,6 +44,19 @@ extension Application { public init() {} + public func validate() throws { + if follow { return } + if let value = Int(last), value > 0 { return } + guard let unit = last.last, "mhd".contains(unit), + let value = Int(last.dropLast()), value > 0 + else { + throw ContainerizationError( + .invalidArgument, + message: "invalid --last value '\(last)': expected a positive integer (seconds) or a value with suffix m, h, or d (e.g. 30, 5m, 1h, 2d)" + ) + } + } + public func run() async throws { let process = Process() let sigHandler = AsyncSignalHandler.create(notify: [SIGINT, SIGTERM]) diff --git a/Tests/CLITests/Subcommands/System/TestCLISystemLogs.swift b/Tests/CLITests/Subcommands/System/TestCLISystemLogs.swift new file mode 100644 index 000000000..1e61eded3 --- /dev/null +++ b/Tests/CLITests/Subcommands/System/TestCLISystemLogs.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// 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 Foundation +import Testing + +@Suite(.serialSuites) +final class TestCLISystemLogs: CLITest { + + @Test func testLogsRejectsInvalidLastUnit() throws { + let (_, _, error, status) = try run(arguments: ["system", "logs", "--last", "1x"]) + #expect(status != 0, "Expected non-zero exit for invalid --last unit") + #expect(error.contains("invalid --last value")) + } + + @Test func testLogsRejectsNonNumericLast() throws { + let (_, _, error, status) = try run(arguments: ["system", "logs", "--last", "abc"]) + #expect(status != 0, "Expected non-zero exit for non-numeric --last") + #expect(error.contains("invalid --last value")) + } + + @Test func testLogsRejectsZeroLast() throws { + let (_, _, error, status) = try run(arguments: ["system", "logs", "--last", "0m"]) + #expect(status != 0, "Expected non-zero exit for zero --last value") + #expect(error.contains("invalid --last value")) + } +}