Skip to content

Commit eeb1930

Browse files
committed
Avoid new-line on progress when never updated
1 parent 85960ea commit eeb1930

File tree

2 files changed

+72
-47
lines changed

2 files changed

+72
-47
lines changed

Sources/Utility/ProgressAnimation.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public final class MultiLineNinjaProgressAnimation: ProgressAnimationProtocol {
9595
/// A redrawing ninja-like progress animation.
9696
public final class RedrawingNinjaProgressAnimation: ProgressAnimationProtocol {
9797
private let terminal: TerminalController
98+
private var hasDisplayedProgress = false
9899

99100
init(terminal: TerminalController) {
100101
self.terminal = terminal
@@ -106,10 +107,13 @@ public final class RedrawingNinjaProgressAnimation: ProgressAnimationProtocol {
106107
terminal.clearLine()
107108
terminal.write("[\(step)/\(total)] ")
108109
terminal.write(text)
110+
hasDisplayedProgress = true
109111
}
110112

111113
public func complete(success: Bool) {
112-
terminal.endLine()
114+
if hasDisplayedProgress {
115+
terminal.endLine()
116+
}
113117
}
114118

115119
public func clear() {

Tests/UtilityTests/ProgressAnimationTests.swift

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,54 +17,43 @@ import TestSupport
1717
typealias Thread = Basic.Thread
1818

1919
final class ProgressAnimationTests: XCTestCase {
20-
func testPercentProgressAnimation() {
21-
guard let pty = PseudoTerminal() else {
22-
XCTFail("Couldn't create pseudo terminal.")
23-
return
24-
}
25-
26-
// Test progress animaton when writing to a non tty stream.
27-
let outStream = BufferedOutputByteStream()
28-
var animation = PercentProgressAnimation(stream: outStream, header: "test")
20+
func testPercentProgressAnimationDumbTerminal() {
21+
var outStream = BufferedOutputByteStream()
22+
var animation = PercentProgressAnimation(stream: outStream, header: "TestHeader")
2923

3024
runProgressAnimation(animation)
3125
XCTAssertEqual(outStream.bytes.asString, """
32-
test
26+
TestHeader
3327
0%: 0
3428
10%: 1
3529
20%: 2
3630
30%: 3
3731
40%: 4
3832
50%: 5
39-
33+
4034
""")
4135

42-
// Test progress bar when writing a tty stream.
43-
animation = PercentProgressAnimation(stream: pty.outStream, header: "TestHeader")
36+
outStream = BufferedOutputByteStream()
37+
animation = PercentProgressAnimation(stream: outStream, header: "TestHeader")
4438

45-
var output = ""
46-
let thread = Thread {
47-
while let out = pty.readMaster() {
48-
output += out
49-
}
50-
}
51-
thread.start()
52-
runProgressAnimation(animation)
53-
pty.closeSlave()
54-
// Make sure to read the complete output before checking it.
55-
thread.join()
56-
pty.closeMaster()
57-
XCTAssertTrue(output.spm_chuzzle()?.hasPrefix("\u{1B}[36m\u{1B}[1mTestHeader\u{1B}[0m") ?? false)
39+
animation.complete(success: true)
40+
XCTAssertEqual(outStream.bytes.asString, "")
5841
}
5942

60-
func testNinjaProgressAnimation() {
61-
guard let pty = PseudoTerminal() else {
62-
XCTFail("Couldn't create pseudo terminal.")
63-
return
43+
func testPercentProgressAnimationTTY() throws {
44+
let output = try readingTTY { tty in
45+
let animation = PercentProgressAnimation(stream: tty.outStream, header: "TestHeader")
46+
runProgressAnimation(animation)
6447
}
6548

66-
// Test progress animaton when writing to a non tty stream.
67-
let outStream = BufferedOutputByteStream()
49+
let startCyan = "\u{1B}[36m"
50+
let bold = "\u{1B}[1m"
51+
let end = "\u{1B}[0m"
52+
XCTAssertMatch(output.spm_chuzzle(), .prefix("\(startCyan)\(bold)TestHeader\(end)"))
53+
}
54+
55+
func testNinjaProgressAnimationDumbTerminal() {
56+
var outStream = BufferedOutputByteStream()
6857
var animation = NinjaProgressAnimation(stream: outStream)
6958

7059
runProgressAnimation(animation)
@@ -78,35 +67,67 @@ final class ProgressAnimationTests: XCTestCase {
7867
7968
""")
8069

81-
// Test progress bar when writing a tty stream.
82-
animation = NinjaProgressAnimation(stream: pty.outStream)
70+
outStream = BufferedOutputByteStream()
71+
animation = NinjaProgressAnimation(stream: outStream)
72+
73+
animation.complete(success: true)
74+
XCTAssertEqual(outStream.bytes.asString, "")
75+
}
76+
77+
func testNinjaProgressAnimationTTY() throws {
78+
var output = try readingTTY { tty in
79+
let animation = NinjaProgressAnimation(stream: tty.outStream)
80+
runProgressAnimation(animation)
81+
}
82+
83+
let clearLine = "\u{1B}[2K\r"
84+
let newline = "\r\n"
85+
XCTAssertEqual(output, """
86+
\(clearLine)[0/10] 0\
87+
\(clearLine)[1/10] 1\
88+
\(clearLine)[2/10] 2\
89+
\(clearLine)[3/10] 3\
90+
\(clearLine)[4/10] 4\
91+
\(clearLine)[5/10] 5\(newline)
92+
""")
93+
94+
output = try readingTTY { tty in
95+
let animation = NinjaProgressAnimation(stream: tty.outStream)
96+
animation.complete(success: true)
97+
}
98+
99+
XCTAssertEqual(output, "")
100+
}
101+
102+
private func readingTTY(_ closure: (PseudoTerminal) -> Void) throws -> String {
103+
guard let terminal = PseudoTerminal() else {
104+
struct PseudoTerminalCreationError: Error {}
105+
throw PseudoTerminalCreationError()
106+
}
83107

84108
var output = ""
85109
let thread = Thread {
86-
while let out = pty.readMaster() {
110+
while let out = terminal.readMaster() {
87111
output += out
88112
}
89113
}
114+
90115
thread.start()
91-
runProgressAnimation(animation)
92-
pty.closeSlave()
116+
closure(terminal)
117+
terminal.closeSlave()
118+
93119
// Make sure to read the complete output before checking it.
94120
thread.join()
95-
pty.closeMaster()
96-
XCTAssertEqual(output.spm_chuzzle(), """
97-
\u{1B}[2K\r[0/10] 0\
98-
\u{1B}[2K\r[1/10] 1\
99-
\u{1B}[2K\r[2/10] 2\
100-
\u{1B}[2K\r[3/10] 3\
101-
\u{1B}[2K\r[4/10] 4\
102-
\u{1B}[2K\r[5/10] 5
103-
""")
121+
terminal.closeMaster()
122+
123+
return output
104124
}
105125

106126
private func runProgressAnimation(_ animation: ProgressAnimationProtocol) {
107127
for i in 0...5 {
108128
animation.update(step: i, total: 10, text: String(i))
109129
}
130+
110131
animation.complete(success: true)
111132
}
112133
}

0 commit comments

Comments
 (0)