master
cpp 238 lines 9.92 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCE2EContainerLogsTests.cpp
8
9 Abstract:
10
11 This file contains end-to-end tests for WSLC container logs.
12 --*/
13
14 #include "precomp.h"
15 #include "windows/Common.h"
16 #include "WSLCExecutor.h"
17 #include "WSLCE2EHelpers.h"
18 #include "TestImageRegistry.h"
19
20 namespace WSLCE2ETests {
21
22 class WSLCE2EContainerLogsTests
23 {
24 WSLC_TEST_CLASS(WSLCE2EContainerLogsTests)
25
26 TEST_CLASS_SETUP(ClassSetup)
27 {
28 TestImageRegistry::Instance().EnsureLoaded(DebianImage);
29 return true;
30 }
31
32 TEST_CLASS_CLEANUP(ClassCleanup)
33 {
34 EnsureContainerDoesNotExist(WslcContainerName);
35 return true;
36 }
37
38 TEST_METHOD_SETUP(TestMethodSetup)
39 {
40 EnsureContainerDoesNotExist(WslcContainerName);
41 return true;
42 }
43
44 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_Tail)
45 {
46 // Run a container that outputs two lines
47 auto result = RunWslc(std::format(
48 L"container run --name {} {} sh -c \"echo line1 && echo line2\"", WslcContainerName, DebianImage.NameAndTag()));
49 result.Verify({.Stdout = L"line1\nline2\n", .Stderr = L"", .ExitCode = 0});
50
51 // Verify --tail 1 only shows the last line
52 result = RunWslc(std::format(L"container logs --tail 1 {}", WslcContainerName));
53 result.Verify({.Stdout = L"line2\n", .Stderr = L"", .ExitCode = 0});
54
55 // Verify -n 2 shows both lines
56 result = RunWslc(std::format(L"container logs -n 2 {}", WslcContainerName));
57 result.Verify({.Stdout = L"line1\nline2\n", .Stderr = L"", .ExitCode = 0});
58 }
59
60 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_Timestamps)
61 {
62 // Run a container that outputs a line
63 auto result =
64 RunWslc(std::format(L"container run --name {} {} sh -c \"echo hello\"", WslcContainerName, DebianImage.NameAndTag()));
65 result.Verify({.Stderr = L"", .ExitCode = 0});
66
67 // Verify --timestamps adds timestamp prefix to log lines
68 result = RunWslc(std::format(L"container logs --timestamps {}", WslcContainerName));
69 result.Verify({.Stderr = L"", .ExitCode = 0});
70
71 // Timestamps should be in RFC3339 format (e.g. "2024-01-01T00:00:00.000000000Z hello")
72 // Verify output contains a timestamp-like prefix followed by the message
73 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"hello"));
74 auto lines = result.GetStdoutLines();
75 VERIFY_IS_TRUE(!lines.empty());
76 // Validate RFC3339 structure: YYYY-MM-DDTHH:MM:SS at the start of the line
77 VERIFY_IS_TRUE(lines[0].size() >= 20);
78 VERIFY_ARE_EQUAL(lines[0][4], L'-');
79 VERIFY_ARE_EQUAL(lines[0][7], L'-');
80 VERIFY_ARE_EQUAL(lines[0][10], L'T');
81 VERIFY_ARE_EQUAL(lines[0][13], L':');
82 VERIFY_ARE_EQUAL(lines[0][16], L':');
83 }
84
85 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_TimestampsShortFlag)
86 {
87 // Run a container that outputs a line
88 auto result =
89 RunWslc(std::format(L"container run --name {} {} sh -c \"echo world\"", WslcContainerName, DebianImage.NameAndTag()));
90 result.Verify({.Stderr = L"", .ExitCode = 0});
91
92 // Verify -t (short for --timestamps) works the same way
93 result = RunWslc(std::format(L"container logs -t {}", WslcContainerName));
94 result.Verify({.Stderr = L"", .ExitCode = 0});
95
96 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"world"));
97 auto lines = result.GetStdoutLines();
98 VERIFY_IS_TRUE(!lines.empty());
99 VERIFY_IS_TRUE(lines[0].size() >= 20);
100 VERIFY_ARE_EQUAL(lines[0][4], L'-');
101 VERIFY_ARE_EQUAL(lines[0][7], L'-');
102 VERIFY_ARE_EQUAL(lines[0][10], L'T');
103 VERIFY_ARE_EQUAL(lines[0][13], L':');
104 VERIFY_ARE_EQUAL(lines[0][16], L':');
105 }
106
107 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_Since)
108 {
109 // Run a container that outputs a line
110 auto result =
111 RunWslc(std::format(L"container run --name {} {} sh -c \"echo before\"", WslcContainerName, DebianImage.NameAndTag()));
112 result.Verify({.Stderr = L"", .ExitCode = 0});
113
114 // Using --since 0 should return all logs (epoch start)
115 result = RunWslc(std::format(L"container logs --since 0 {}", WslcContainerName));
116 result.Verify({.Stderr = L"", .ExitCode = 0});
117 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"before"));
118
119 // Using --since with a far-future timestamp should return no logs
120 result = RunWslc(std::format(L"container logs --since 9999999999 {}", WslcContainerName));
121 result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
122 }
123
124 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_SinceRfc3339)
125 {
126 // Run a container that outputs a line
127 auto result =
128 RunWslc(std::format(L"container run --name {} {} sh -c \"echo rfc3339test\"", WslcContainerName, DebianImage.NameAndTag()));
129 result.Verify({.Stderr = L"", .ExitCode = 0});
130
131 // Using --since with an RFC3339 timestamp in the past should return all logs
132 result = RunWslc(std::format(L"container logs --since 2000-01-01T00:00:00Z {}", WslcContainerName));
133 result.Verify({.Stderr = L"", .ExitCode = 0});
134 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"rfc3339test"));
135
136 // Using --since with an RFC3339 timestamp far in the future should return no logs
137 result = RunWslc(std::format(L"container logs --since 2099-12-31T23:59:59Z {}", WslcContainerName));
138 result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
139
140 // Using --since with an RFC3339 timestamp with timezone offset
141 result = RunWslc(std::format(L"container logs --since 2000-01-01T00:00:00+00:00 {}", WslcContainerName));
142 result.Verify({.Stderr = L"", .ExitCode = 0});
143 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"rfc3339test"));
144 }
145
146 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_Until)
147 {
148 // Run a container that outputs a line
149 auto result =
150 RunWslc(std::format(L"container run --name {} {} sh -c \"echo test_until\"", WslcContainerName, DebianImage.NameAndTag()));
151 result.Verify({.Stderr = L"", .ExitCode = 0});
152
153 // Using --until with a far-future timestamp should return all logs
154 result = RunWslc(std::format(L"container logs --until 9999999999 {}", WslcContainerName));
155 result.Verify({.Stderr = L"", .ExitCode = 0});
156 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"test_until"));
157
158 // Using --until 1 (epoch + 1 second) should return no logs since container was created much later
159 result = RunWslc(std::format(L"container logs --until 1 {}", WslcContainerName));
160 result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
161 }
162
163 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_SinceAndUntilCombined)
164 {
165 // Run a container that outputs a line
166 auto result =
167 RunWslc(std::format(L"container run --name {} {} sh -c \"echo combined\"", WslcContainerName, DebianImage.NameAndTag()));
168 result.Verify({.Stderr = L"", .ExitCode = 0});
169
170 // Using --since 0 --until far-future should return all logs
171 result = RunWslc(std::format(L"container logs --since 0 --until 9999999999 {}", WslcContainerName));
172 result.Verify({.Stderr = L"", .ExitCode = 0});
173 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"combined"));
174
175 // Using --since far-future --until far-future should return no logs
176 result = RunWslc(std::format(L"container logs --since 9999999999 --until 9999999999 {}", WslcContainerName));
177 result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
178 }
179
180 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_AllOptionsCombined)
181 {
182 // Run a container that outputs multiple lines
183 auto result = RunWslc(std::format(
184 L"container run --name {} {} sh -c \"echo a && echo b && echo c\"", WslcContainerName, DebianImage.NameAndTag()));
185 result.Verify({.Stderr = L"", .ExitCode = 0});
186
187 // Combine --timestamps --since 0 --tail 2
188 result = RunWslc(std::format(L"container logs --timestamps --since 0 --tail 2 {}", WslcContainerName));
189 result.Verify({.Stderr = L"", .ExitCode = 0});
190
191 // Should have at most 2 lines (from --tail 2) and each should have a timestamp
192 auto lines = result.GetStdoutLines();
193 VERIFY_IS_TRUE(lines.size() <= 2);
194 for (const auto& line : lines)
195 {
196 if (!line.empty())
197 {
198 // Validate RFC3339 structure: YYYY-MM-DDTHH:MM:SS at the start of the line
199 VERIFY_IS_TRUE(line.size() >= 20);
200 VERIFY_ARE_EQUAL(line[4], L'-');
201 VERIFY_ARE_EQUAL(line[7], L'-');
202 VERIFY_ARE_EQUAL(line[10], L'T');
203 VERIFY_ARE_EQUAL(line[13], L':');
204 VERIFY_ARE_EQUAL(line[16], L':');
205 }
206 }
207 }
208
209 WSLC_TEST_METHOD(WSLCE2E_Container_Logs_Follow)
210 {
211 // Sleep between the two lines so the second is not produced until after line 1 is read.
212 constexpr int InterLineSleepSeconds = 2;
213 auto result = RunWslc(std::format(
214 L"container run -d --name {} {} sh -c \"echo follow-line-1; sleep {}; echo follow-line-2\"",
215 WslcContainerName,
216 DebianImage.NameAndTag(),
217 InterLineSleepSeconds));
218 result.Verify({.Stderr = L"", .ExitCode = 0});
219
220 auto logsSession = RunWslcInteractive(std::format(L"container logs -f {}", WslcContainerName));
221
222 // If --follow buffered, line 1 would arrive only after the container exited.
223 logsSession.ExpectStdout("follow-line-1\n");
224 VERIFY_IS_TRUE(logsSession.IsRunning(), L"`logs -f` should still be running mid-sleep after line 1");
225
226 logsSession.ExpectStdout("follow-line-2\n");
227
228 auto exitCode = logsSession.Wait(30000);
229 VERIFY_ARE_EQUAL(0, exitCode);
230 logsSession.VerifyNoErrors();
231 }
232
233 private:
234 const std::wstring WslcContainerName = L"wslc-test-logs";
235 const TestImage& DebianImage = DebianTestImage();
236 };
237
238 } // namespace WSLCE2ETests