master
cpp 164 lines 6.51 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCCLIBuildImageCallbackUnitTests.cpp
8
9 Abstract:
10
11 Unit tests for BuildImageCallback progress rendering.
12
13 --*/
14
15 #include "precomp.h"
16 #include "windows/Common.h"
17 #include "WSLCCLITestHelpers.h"
18
19 #include "BuildImageCallback.h"
20 #include "ContainerModel.h"
21 #include "Terminal.h"
22
23 using namespace wsl::windows::wslc;
24 using namespace wsl::windows::wslc::models;
25 using namespace wsl::windows::wslc::services;
26 using namespace WSLCTestHelpers;
27 using namespace WEX::Logging;
28 using namespace WEX::Common;
29 using namespace WEX::TestExecution;
30
31 namespace WSLCCLIBuildImageCallbackUnitTests {
32
33 namespace {
34
35 // Drives a callback through a representative build: a step header, multi-line log output,
36 // a \r-based in-place progress update, and a pull progress entry. Returns everything the
37 // terminal emitted, captured after the callback is destroyed so its final frame is included.
38 std::wstring RunBuild(ProgressMode mode, bool vtEnabled, bool verbose = false)
39 {
40 CaptureTerminal capture(vtEnabled);
41 wil::unique_event cancelEvent;
42 cancelEvent.create(wil::EventOptions::ManualReset);
43
44 {
45 BuildImageCallback callback(capture.terminal, cancelEvent.get(), verbose, mode);
46
47 VERIFY_SUCCEEDED(callback.OnProgress("#1 [1/2] FROM debian:latest\n", "", 0, 0));
48 VERIFY_SUCCEEDED(callback.OnProgress("first log line\nsecond log line\n", "log", 0, 0));
49 VERIFY_SUCCEEDED(callback.OnProgress("downloading 10%\rdownloading 80%\r", "log", 0, 0));
50 VERIFY_SUCCEEDED(callback.OnProgress("sha256:abc downloading", "sha256:abc", 50, 100));
51 VERIFY_SUCCEEDED(callback.OnProgress("#2 [2/2] RUN echo hi\n", "", 0, 0));
52 }
53
54 return capture.captured();
55 }
56
57 // Drives the same build as RunBuild, but destroys the callback while an exception is in flight,
58 // which is the only condition under which the destructor replays captured output.
59 std::wstring RunFailingBuild(ProgressMode mode, bool vtEnabled)
60 {
61 CaptureTerminal capture(vtEnabled);
62 wil::unique_event cancelEvent;
63 cancelEvent.create(wil::EventOptions::ManualReset);
64
65 try
66 {
67 BuildImageCallback callback(capture.terminal, cancelEvent.get(), false, mode);
68
69 VERIFY_SUCCEEDED(callback.OnProgress("#1 [1/2] FROM debian:latest\n", "", 0, 0));
70 VERIFY_SUCCEEDED(callback.OnProgress("sha256:abc downloading", "sha256:abc", 50, 100));
71 VERIFY_SUCCEEDED(callback.OnProgress("#2 [2/2] RUN exit 7\n", "", 0, 0));
72 VERIFY_SUCCEEDED(callback.OnProgress(" | about-to-fail\n", "log", 0, 0));
73 VERIFY_SUCCEEDED(callback.OnProgress("process \"/bin/sh -c exit 7\" did not complete successfully: exit code: 7\n", "", 0, 0));
74
75 THROW_HR(E_FAIL);
76 }
77 catch (...)
78 {
79 }
80
81 return capture.captured();
82 }
83
84 } // namespace
85
86 class WSLCCLIBuildImageCallbackUnitTests
87 {
88 WSLC_TEST_CLASS(WSLCCLIBuildImageCallbackUnitTests)
89
90 TEST_CLASS_SETUP(TestClassSetup)
91 {
92 return true;
93 }
94
95 TEST_CLASS_CLEANUP(TestClassCleanup)
96 {
97 return true;
98 }
99
100 // plain must never redraw. Even when a VT console is attached (the case an E2E test with
101 // redirected output cannot reach), the output must be append-only: no cursor movement, no
102 // erases, and no escape sequences of any kind.
103 TEST_METHOD(BuildImageCallback_PlainOnVtConsole_EmitsNoEscapeSequences)
104 {
105 const auto output = RunBuild(ProgressMode::Plain, true);
106
107 VERIFY_IS_TRUE(output.find(L'\x1b') == std::wstring::npos, L"plain mode must not emit any VT escape sequence");
108 VERIFY_IS_TRUE(output.find(L"#1 [1/2] FROM debian:latest") != std::wstring::npos, L"build steps must still be reported");
109 VERIFY_IS_TRUE(output.find(L"#2 [2/2] RUN echo hi") != std::wstring::npos, L"build steps must still be reported");
110 }
111
112 // Control: the same input in tty mode does emit cursor control. Without this, the test above
113 // could pass simply because the rendering path was never exercised.
114 TEST_METHOD(BuildImageCallback_TtyOnVtConsole_EmitsEscapeSequences)
115 {
116 const auto output = RunBuild(ProgressMode::Tty, true);
117
118 VERIFY_IS_TRUE(output.find(L'\x1b') != std::wstring::npos, L"tty mode is expected to render in place using VT sequences");
119 }
120
121 // plain must produce the same escape-free output regardless of whether a console is attached,
122 // so redirecting a plain build to a file yields exactly what was shown on screen.
123 TEST_METHOD(BuildImageCallback_PlainMatchesRedirectedOutput)
124 {
125 const auto onConsole = RunBuild(ProgressMode::Plain, true);
126 const auto redirected = RunBuild(ProgressMode::Plain, false);
127
128 VERIFY_ARE_EQUAL(redirected, onConsole);
129 }
130
131 // quiet suppresses progress entirely on success, and must not emit cursor control either.
132 // This also covers the success side of replay: nothing captured may reach the terminal.
133 TEST_METHOD(BuildImageCallback_QuietOnVtConsole_EmitsNothing)
134 {
135 const auto output = RunBuild(ProgressMode::Quiet, true);
136
137 VERIFY_ARE_EQUAL(std::wstring{L""}, output);
138 }
139
140 // quiet prints nothing while the build runs, but a failure must still explain what went wrong.
141 // The step that failed and the error itself are reported with an empty id rather than "log", so
142 // they have to be captured too or the replay only shows unattributed log output.
143 TEST_METHOD(BuildImageCallback_QuietReplaysFailingStepAndError)
144 {
145 const auto output = RunFailingBuild(ProgressMode::Quiet, true);
146
147 VERIFY_IS_TRUE(output.find(L"#2 [2/2] RUN exit 7") != std::wstring::npos, L"quiet must replay the step that failed");
148 VERIFY_IS_TRUE(
149 output.find(L"did not complete successfully: exit code: 7") != std::wstring::npos,
150 L"quiet must replay the build error");
151 VERIFY_IS_TRUE(output.find(L"about-to-fail") != std::wstring::npos, L"quiet must replay log output");
152 }
153
154 // Pull progress is rewritten in place and is the one message sent without a trailing newline,
155 // so replaying it would emit a partial line into an otherwise line-oriented transcript.
156 TEST_METHOD(BuildImageCallback_QuietReplayOmitsPullProgress)
157 {
158 const auto output = RunFailingBuild(ProgressMode::Quiet, true);
159
160 VERIFY_IS_TRUE(output.find(L"sha256:abc downloading") == std::wstring::npos, L"quiet must not replay pull progress");
161 }
162 };
163
164 } // namespace WSLCCLIBuildImageCallbackUnitTests