@samitouri / QOSAMI-WSL / commits / 7e37af60

wslc: emit --format json output on a single line (#41268)

* wslc: emit --format json output on a single line * clang format

AmirMS committed Aug 6, 2026 at 14:33 UTC 7e37af6048b51a7279b836bd11f3a0f3e62a5803
33 files changed +210 -89
localization/strings/en-US/Resources.resw
+7 -3
@@ -2955,6 +2955,10 @@ On first run, creates the file with all settings commented out at their defaults
2955 <value>Output formatting (json or table) (Default: table)</value>
2956 <comment>{Locked="json"}{Locked="table"}Command line arguments should not be translated</comment>
2957 </data>
2958 + <data name="WSLCCLI_InspectFormatArgDescription" xml:space="preserve">
2959 + <value>Output formatting (json for single-line output) (Default: indented JSON)</value>
2960 + <comment>{Locked="json"}Command line arguments should not be translated</comment>
2961 + </data>
2962 <data name="WSLCCLI_ForwardArgsDescription" xml:space="preserve">
2963 <value>Arguments to pass to container's init process</value>
2964 </data>
@@ -3219,9 +3223,9 @@ On first run, creates the file with all settings commented out at their defaults
3223 <data name="WSLCCLI_SessionListVerboseArgDescription" xml:space="preserve">
3224 <value>Show detailed information about the listed sessions.</value>
3225 </data>
3222 - <data name="WSLCCLI_InvalidFormatError" xml:space="preserve">
3223 - <value>Invalid format type specified. Supported format types are: json, table</value>
3224 - <comment>{Locked="json"}{Locked="table"}Command line arguments should not be translated</comment>
3226 + <data name="WSLCCLI_InvalidFormatValueError" xml:space="preserve">
3227 + <value>Invalid {} value: {} is not a recognized format type. Supported format types are: {}.</value>
3228 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3229 </data>
3230 <data name="WSLCCLI_InvalidInspectError" xml:space="preserve">
3231 <value>Invalid {} value: {} is not a recognized inspect type. Supported inspect types are: {}.</value>
src/shared/inc/JsonUtils.h
+3
@@ -34,6 +34,9 @@ namespace wsl::shared {
34
35 constexpr int c_jsonPrettyPrintIndent = 2;
36
37 +// A negative indent makes nlohmann::json::dump() emit the document on a single line.
38 +constexpr int c_jsonCompactIndent = -1;
39 +
40 struct EmptyObject
41 {
42 };
src/windows/wslc/arguments/ArgumentDefinitions.h
+1
@@ -77,6 +77,7 @@ _(ImageForce, "force", L"f", Kind::Flag, L
77 _(ImageId, "image", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ImageIdArgDescription()) \
78 _(ImportFile, "file", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ImportFileArgDescription()) \
79 _(Input, "input", L"i", Kind::Value, Localization::WSLCCLI_InputArgDescription()) \
80 +_(InspectFormat, "format", NO_ALIAS, Kind::Value, Localization::WSLCCLI_InspectFormatArgDescription()) \
81 _(Interactive, "interactive", L"i", Kind::Flag, Localization::WSLCCLI_InteractiveArgDescription()) \
82 _(Internal, "internal", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NetworkInternalArgDescription()) \
83 _(IpAddress, "ip", NO_ALIAS, Kind::Value, Localization::WSLCCLI_IpAddressArgDescription()) \
src/windows/wslc/arguments/ArgumentValidation.cpp
+17
@@ -37,6 +37,10 @@ void Argument::Validate(const ArgMap& execArgs) const
37 validation::ValidateFormatTypeFromString(execArgs.GetAll<ArgType::Format>(), m_name);
38 break;
39
40 + case ArgType::InspectFormat:
41 + validation::ValidateInspectFormatTypeFromString(execArgs.GetAll<ArgType::InspectFormat>(), m_name);
42 + break;
43 +
44 case ArgType::Signal:
45 validation::ValidateWSLCSignalFromString(execArgs.GetAll<ArgType::Signal>(), m_name);
46 break;
@@ -224,6 +228,19 @@ void ValidateFormatTypeFromString(const std::vector<std::wstring>& values, const
228 }
229 }
230
231 +void ValidateInspectFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName)
232 +{
233 + constexpr std::wstring_view supportedValues = L"json";
234 +
235 + for (const auto& value : values)
236 + {
237 + if (!IsEqual(value, L"json"))
238 + {
239 + throw ArgumentException(Localization::WSLCCLI_InvalidFormatValueError(argName, value, supportedValues));
240 + }
241 + }
242 +}
243 +
244 void ValidateGpus(const std::vector<std::wstring>& values, const std::wstring& argName)
245 {
246 for (const auto& value : values)
src/windows/wslc/arguments/ArgumentValidation.h
+3
@@ -74,6 +74,9 @@ void ValidateUlimit(const std::vector<std::wstring>& values, const std::wstring&
74
75 void ValidateFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName);
76
77 +// The inspect family only renders JSON, so `json` (single line) is the sole accepted value.
78 +void ValidateInspectFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName);
79 +
80 void ValidateGpus(const std::vector<std::wstring>& values, const std::wstring& argName);
81
82 void ValidateVolumeMount(const std::vector<std::wstring>& values);
src/windows/wslc/arguments/SpecParsing.cpp
+19 -2
@@ -19,6 +19,7 @@ Abstract:
19 #include "ArgumentValidation.h"
20 #include "Exceptions.h"
21 #include "ImageService.h"
22 +#include "JsonUtils.h"
23 #include "Localization.h"
24 #include <algorithm>
25 #include <charconv>
@@ -621,11 +622,27 @@ models::FormatType GetFormatTypeFromString(const std::wstring& input, const std:
622 }
623 else
624 {
624 - throw ArgumentException(std::format(
625 - L"Invalid {} value: {} is not a recognized format type. Supported format types are: json, table.", argName, input));
625 + constexpr std::wstring_view supportedValues = L"json, table";
626 + throw ArgumentException(Localization::WSLCCLI_InvalidFormatValueError(argName, input, supportedValues));
627 }
628 }
629
630 +models::FormatType GetOutputFormat(const argument::ArgMap& args)
631 +{
632 + if (!args.Contains(argument::ArgType::Format))
633 + {
634 + return models::FormatType::Table;
635 + }
636 +
637 + return GetFormatTypeFromString(args.Get<argument::ArgType::Format>());
638 +}
639 +
640 +int GetInspectJsonIndent(const argument::ArgMap& args)
641 +{
642 + // Validation guarantees the only accepted value is "json", so its presence alone selects compact.
643 + return args.Contains(argument::ArgType::InspectFormat) ? wsl::shared::c_jsonCompactIndent : wsl::shared::c_jsonPrettyPrintIndent;
644 +}
645 +
646 models::InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstring& argName)
647 {
648 if (IsEqual(input, L"image"))
src/windows/wslc/arguments/SpecParsing.h
+7
@@ -14,6 +14,7 @@ Abstract:
14 --*/
15 #pragma once
16
17 +#include "ArgumentTypes.h"
18 #include "ContainerModel.h"
19 #include "InspectModel.h"
20 #include <string>
@@ -81,6 +82,12 @@ ULONGLONG GetTimestampFromString(const std::wstring& value, const std::wstring&
82 // Parses an output format ("json"/"table") into a FormatType.
83 models::FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {});
84
85 +// Resolves the --format argument, falling back to table when it was not supplied.
86 +models::FormatType GetOutputFormat(const argument::ArgMap& args);
87 +
88 +// Returns the json::dump() indent for the inspect family: compact for `--format json`, indented otherwise.
89 +int GetInspectJsonIndent(const argument::ArgMap& args);
90 +
91 // Parses an inspect target ("image"/"container"/"network"/"volume") into an InspectType.
92 models::InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstring& argName);
93
src/windows/wslc/commands/ContainerCommand.h
-1
@@ -225,7 +225,6 @@ struct ContainerStatsCommand final : public Command
225 std::wstring LongDescription() const override;
226
227 protected:
228 - void ValidateArgumentsInternal(const ArgMap& execArgs) const override;
228 void ExecuteInternal(CLIExecutionContext& context) const override;
229 };
230
src/windows/wslc/commands/ContainerInspectCommand.cpp
+1
@@ -28,6 +28,7 @@ std::vector<Argument> ContainerInspectCommand::GetArguments() const
28 {
29 return {
30 Argument::Create(ArgType::ContainerId, true, Limit::Unlimited),
31 + Argument::Create(ArgType::InspectFormat),
32 };
33 }
34
src/windows/wslc/commands/ContainerStatsCommand.cpp
-13
@@ -21,7 +21,6 @@ Abstract:
21 using namespace wsl::windows::wslc::execution;
22 using namespace wsl::windows::wslc::task;
23 using namespace wsl::shared;
24 -using namespace wsl::shared::string;
24
25 namespace wsl::windows::wslc {
26 std::vector<Argument> ContainerStatsCommand::GetArguments() const
@@ -44,18 +43,6 @@ std::wstring ContainerStatsCommand::LongDescription() const
43 return Localization::WSLCCLI_ContainerStatsLongDesc();
44 }
45
47 -void ContainerStatsCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const
48 -{
49 - if (execArgs.Contains(ArgType::Format))
50 - {
51 - auto format = execArgs.Get<ArgType::Format>();
52 - if (!IsEqual(format, L"json") && !IsEqual(format, L"table"))
53 - {
54 - throw CommandException(Localization::WSLCCLI_InvalidFormatError());
55 - }
56 - }
57 -}
58 -
46 void ContainerStatsCommand::ExecuteInternal(CLIExecutionContext& context) const
47 {
48 context //
src/windows/wslc/commands/ImageInspectCommand.cpp
+1
@@ -29,6 +29,7 @@ std::vector<Argument> ImageInspectCommand::GetArguments() const
29 {
30 return {
31 Argument::Create(ArgType::ImageId, true, Limit::Unlimited),
32 + Argument::Create(ArgType::InspectFormat),
33 };
34 }
35
src/windows/wslc/commands/InspectCommand.cpp
+1
@@ -24,6 +24,7 @@ std::vector<Argument> InspectCommand::GetArguments() const
24 return {
25 Argument::Create(ArgType::ObjectId, true, Limit::Unlimited),
26 Argument::Create(ArgType::Type),
27 + Argument::Create(ArgType::InspectFormat),
28 };
29 }
30
src/windows/wslc/commands/NetworkCommand.h
-1
@@ -89,7 +89,6 @@ struct NetworkListCommand final : public Command
89 std::wstring LongDescription() const override;
90
91 protected:
92 - void ValidateArgumentsInternal(const ArgMap& execArgs) const override;
92 void ExecuteInternal(CLIExecutionContext& context) const override;
93 };
94
src/windows/wslc/commands/NetworkInspectCommand.cpp
+1
@@ -28,6 +28,7 @@ std::vector<Argument> NetworkInspectCommand::GetArguments() const
28 {
29 return {
30 Argument::Create(ArgType::NetworkName, true, Limit::Unlimited),
31 + Argument::Create(ArgType::InspectFormat),
32 };
33 }
34
src/windows/wslc/commands/NetworkListCommand.cpp
-13
@@ -21,7 +21,6 @@ Abstract:
21 using namespace wsl::windows::wslc::execution;
22 using namespace wsl::windows::wslc::task;
23 using namespace wsl::shared;
24 -using namespace wsl::shared::string;
24
25 namespace wsl::windows::wslc {
26 // Network List Command
@@ -43,18 +42,6 @@ std::wstring NetworkListCommand::LongDescription() const
42 return Localization::WSLCCLI_NetworkListLongDesc();
43 }
44
46 -void NetworkListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const
47 -{
48 - if (execArgs.Contains(ArgType::Format))
49 - {
50 - auto format = execArgs.Get<ArgType::Format>();
51 - if (!IsEqual(format, L"json") && !IsEqual(format, L"table"))
52 - {
53 - throw CommandException(Localization::WSLCCLI_InvalidFormatError());
54 - }
55 - }
56 -}
57 -
45 void NetworkListCommand::ExecuteInternal(CLIExecutionContext& context) const
46 {
47 context << ResolveSession //
src/windows/wslc/commands/VersionCommand.cpp
+2 -6
@@ -47,11 +47,7 @@ void VersionCommand::PrintVersion(Reporter& reporter)
47
48 void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const
49 {
50 - FormatType format = FormatType::Table;
51 - if (context.Args.Contains(ArgType::Format))
52 - {
53 - format = validation::GetFormatTypeFromString(context.Args.Get<ArgType::Format>());
54 - }
50 + FormatType format = validation::GetOutputFormat(context.Args);
51
52 switch (format)
53 {
@@ -59,7 +55,7 @@ void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const
55 {
56 nlohmann::json root;
57 root["Client"]["Version"] = std::string{WSL_PACKAGE_VERSION};
62 - context.Reporter.Output(L"{}\n", MultiByteToWide(root.dump(c_jsonPrettyPrintIndent)));
58 + context.Reporter.Output(L"{}\n", MultiByteToWide(root.dump(c_jsonCompactIndent)));
59 break;
60 }
61 case FormatType::Table:
src/windows/wslc/commands/VolumeCommand.h
-1
@@ -89,7 +89,6 @@ struct VolumeListCommand final : public Command
89 std::wstring LongDescription() const override;
90
91 protected:
92 - void ValidateArgumentsInternal(const ArgMap& execArgs) const override;
92 void ExecuteInternal(CLIExecutionContext& context) const override;
93 };
94
src/windows/wslc/commands/VolumeInspectCommand.cpp
+1
@@ -28,6 +28,7 @@ std::vector<Argument> VolumeInspectCommand::GetArguments() const
28 {
29 return {
30 Argument::Create(ArgType::VolumeName, true, Limit::Unlimited),
31 + Argument::Create(ArgType::InspectFormat),
32 };
33 }
34
src/windows/wslc/commands/VolumeListCommand.cpp
-13
@@ -21,7 +21,6 @@ Abstract:
21 using namespace wsl::windows::wslc::execution;
22 using namespace wsl::windows::wslc::task;
23 using namespace wsl::shared;
24 -using namespace wsl::shared::string;
24
25 namespace wsl::windows::wslc {
26 // Volume List Command
@@ -43,18 +42,6 @@ std::wstring VolumeListCommand::LongDescription() const
42 return Localization::WSLCCLI_VolumeListLongDesc();
43 }
44
46 -void VolumeListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const
47 -{
48 - if (execArgs.Contains(ArgType::Format))
49 - {
50 - auto format = execArgs.Get<ArgType::Format>();
51 - if (!IsEqual(format, L"json") && !IsEqual(format, L"table"))
52 - {
53 - throw CommandException(Localization::WSLCCLI_InvalidFormatError());
54 - }
55 - }
56 -}
57 -
45 void VolumeListCommand::ExecuteInternal(CLIExecutionContext& context) const
46 {
47 context << ResolveSession //
src/windows/wslc/tasks/ContainerTasks.cpp
+5 -13
@@ -238,7 +238,7 @@ void InspectContainers(CLIExecutionContext& context)
238 }
239 }
240
241 - auto json = ToJson(result, c_jsonPrettyPrintIndent);
241 + auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
242 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
243 }
244
@@ -565,17 +565,13 @@ void ListContainers(CLIExecutionContext& context)
565 return;
566 }
567
568 - FormatType format = FormatType::Table; // Default is table
569 - if (context.Args.Contains(ArgType::Format))
570 - {
571 - format = validation::GetFormatTypeFromString(context.Args.Get<ArgType::Format>());
572 - }
568 + FormatType format = validation::GetOutputFormat(context.Args);
569
570 switch (format)
571 {
572 case FormatType::Json:
573 {
578 - auto json = ToJson(containers, c_jsonPrettyPrintIndent);
574 + auto json = ToJson(containers, c_jsonCompactIndent);
575 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
576 break;
577 }
@@ -975,17 +971,13 @@ void ShowContainerStats(CLIExecutionContext& context)
971 10 // Batch Size - chosen to be around typical expected container use while protecting against extreme cases.
972 );
973
978 - FormatType format = FormatType::Table; // Default is table
979 - if (context.Args.Contains(ArgType::Format))
980 - {
981 - format = validation::GetFormatTypeFromString(context.Args.Get<ArgType::Format>());
982 - }
974 + FormatType format = validation::GetOutputFormat(context.Args);
975
976 switch (format)
977 {
978 case FormatType::Json:
979 {
988 - context.Reporter.Output(L"{}\n", MultiByteToWide(statsJson.dump(c_jsonPrettyPrintIndent)));
980 + context.Reporter.Output(L"{}\n", MultiByteToWide(statsJson.dump(c_jsonCompactIndent)));
981 break;
982 }
983 case FormatType::Table:
src/windows/wslc/tasks/ImageTasks.cpp
+3 -7
@@ -184,17 +184,13 @@ void ListImages(CLIExecutionContext& context)
184 return;
185 }
186
187 - FormatType format = FormatType::Table; // Default is table
188 - if (context.Args.Contains(ArgType::Format))
189 - {
190 - format = validation::GetFormatTypeFromString(context.Args.Get<ArgType::Format>());
191 - }
187 + FormatType format = validation::GetOutputFormat(context.Args);
188
189 switch (format)
190 {
191 case FormatType::Json:
192 {
197 - auto json = ToJson(images, c_jsonPrettyPrintIndent);
193 + auto json = ToJson(images, c_jsonCompactIndent);
194 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
195 break;
196 }
@@ -350,7 +346,7 @@ void InspectImages(CLIExecutionContext& context)
346 }
347 }
348
353 - auto json = ToJson(result, c_jsonPrettyPrintIndent);
349 + auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
350 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
351 }
352
src/windows/wslc/tasks/InspectTasks.cpp
+1 -1
@@ -113,6 +113,6 @@ void Inspect(CLIExecutionContext& context)
113 }
114
115 // Always print the array, even if it's empty or an error was encountered
116 - context.Reporter.Output(L"{}\n", MultiByteToWide(array.dump(c_jsonPrettyPrintIndent)));
116 + context.Reporter.Output(L"{}\n", MultiByteToWide(array.dump(validation::GetInspectJsonIndent(context.Args))));
117 }
118 } // namespace wsl::windows::wslc::task
src/windows/wslc/tasks/NetworkTasks.cpp
+3 -7
@@ -161,7 +161,7 @@ void InspectNetworks(CLIExecutionContext& context)
161 }
162 }
163
164 - auto json = ToJson(result, c_jsonPrettyPrintIndent);
164 + auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
165 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
166 }
167
@@ -180,17 +180,13 @@ void ListNetworks(CLIExecutionContext& context)
180 return;
181 }
182
183 - FormatType format = FormatType::Table;
184 - if (context.Args.Contains(ArgType::Format))
185 - {
186 - format = validation::GetFormatTypeFromString(context.Args.Get<ArgType::Format>());
187 - }
183 + FormatType format = validation::GetOutputFormat(context.Args);
184
185 switch (format)
186 {
187 case FormatType::Json:
188 {
193 - auto json = ToJson(networks, c_jsonPrettyPrintIndent);
189 + auto json = ToJson(networks, c_jsonCompactIndent);
190 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
191 break;
192 }
src/windows/wslc/tasks/VolumeTasks.cpp
+3 -7
@@ -148,7 +148,7 @@ void InspectVolumes(CLIExecutionContext& context)
148 }
149 }
150
151 - auto json = ToJson(result, c_jsonPrettyPrintIndent);
151 + auto json = ToJson(result, validation::GetInspectJsonIndent(context.Args));
152 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
153 }
154
@@ -167,17 +167,13 @@ void ListVolumes(CLIExecutionContext& context)
167 return;
168 }
169
170 - FormatType format = FormatType::Table;
171 - if (context.Args.Contains(ArgType::Format))
172 - {
173 - format = validation::GetFormatTypeFromString(context.Args.Get<ArgType::Format>());
174 - }
170 + FormatType format = validation::GetOutputFormat(context.Args);
171
172 switch (format)
173 {
174 case FormatType::Json:
175 {
180 - auto json = ToJson(volumes, c_jsonPrettyPrintIndent);
176 + auto json = ToJson(volumes, c_jsonCompactIndent);
177 context.Reporter.Output(L"{}\n", MultiByteToWide(json));
178 break;
179 }
test/windows/wslc/CommandLineTestCases.h
+19
@@ -183,6 +183,14 @@ COMMAND_LINE_TEST_CASE(L"kill cont1 --signal sigkill", L"kill", true)
183 COMMAND_LINE_TEST_CASE(L"container kill cont1 -s KILL", L"kill", true)
184 COMMAND_LINE_TEST_CASE(L"inspect cont1", L"inspect", true)
185 COMMAND_LINE_TEST_CASE(L"container inspect cont1", L"inspect", true)
186 +// --format on the inspect family: json is the only accepted value, so `table` is rejected here.
187 +COMMAND_LINE_TEST_CASE(L"inspect --format json cont1", L"inspect", true)
188 +COMMAND_LINE_TEST_CASE(L"inspect --format table cont1", L"inspect", false)
189 +COMMAND_LINE_TEST_CASE(L"inspect --type container --format json cont1", L"inspect", true)
190 +COMMAND_LINE_TEST_CASE(L"inspect --format badformat cont1", L"inspect", false)
191 +COMMAND_LINE_TEST_CASE(L"container inspect --format json cont1", L"inspect", true)
192 +COMMAND_LINE_TEST_CASE(L"container inspect --format table cont1", L"inspect", false)
193 +COMMAND_LINE_TEST_CASE(L"container inspect --format badformat cont1", L"inspect", false)
194 COMMAND_LINE_TEST_CASE(L"remove cont1", L"remove", true)
195 COMMAND_LINE_TEST_CASE(L"container remove cont1 cont2", L"remove", true)
196 COMMAND_LINE_TEST_CASE(L"rm cont1", L"remove", true)
@@ -299,6 +307,17 @@ COMMAND_LINE_TEST_CASE(L"pull ubuntu --quiet", L"pull", true)
307 COMMAND_LINE_TEST_CASE(L"pull ubuntu -q", L"pull", true)
308 COMMAND_LINE_TEST_CASE(L"image rm cont1 --force --no-prune", L"remove", true)
309 COMMAND_LINE_TEST_CASE(L"image rm cont1 cont2 cont3 --force --no-prune", L"remove", true)
310 +COMMAND_LINE_TEST_CASE(L"image inspect --format json img1", L"inspect", true)
311 +COMMAND_LINE_TEST_CASE(L"image inspect --format table img1", L"inspect", false)
312 +COMMAND_LINE_TEST_CASE(L"image inspect --format badformat img1", L"inspect", false)
313 +
314 +// Network and volume inspect --format tests
315 +COMMAND_LINE_TEST_CASE(L"network inspect --format json net1", L"inspect", true)
316 +COMMAND_LINE_TEST_CASE(L"network inspect --format table net1", L"inspect", false)
317 +COMMAND_LINE_TEST_CASE(L"network inspect --format badformat net1", L"inspect", false)
318 +COMMAND_LINE_TEST_CASE(L"volume inspect --format json vol1", L"inspect", true)
319 +COMMAND_LINE_TEST_CASE(L"volume inspect --format table vol1", L"inspect", false)
320 +COMMAND_LINE_TEST_CASE(L"volume inspect --format badformat vol1", L"inspect", false)
321
322 // Version command tests
323 COMMAND_LINE_TEST_CASE(L"version", L"version", true)
test/windows/wslc/e2e/WSLCE2EContainerInspectTests.cpp
+14
@@ -79,6 +79,20 @@ class WSLCE2EContainerInspectTests
79 VERIFY_ARE_EQUAL(WideToMultiByte(TestContainerName1), inspectData[0].Name);
80 }
81
82 + WSLC_TEST_METHOD(WSLCE2E_Container_Inspect_FormatJson_IsSingleLine)
83 + {
84 + auto createResult = RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag()));
85 + createResult.Verify({.Stderr = L"", .ExitCode = 0});
86 +
87 + auto result = RunWslc(std::format(L"container inspect --format json {}", TestContainerName1));
88 + result.Verify({.Stderr = L"", .ExitCode = 0});
89 +
90 + const auto document = VerifyCompactJsonOutput(result);
91 + VERIFY_IS_TRUE(document.is_array());
92 + VERIFY_ARE_EQUAL(1u, document.size());
93 + VERIFY_ARE_EQUAL(WideToMultiByte(TestContainerName1), document[0]["Name"].get<std::string>());
94 + }
95 +
96 WSLC_TEST_METHOD(WSLCE2E_Container_InspectMultiple_Success)
97 {
98 // Create two containers to inspect at the same time
test/windows/wslc/e2e/WSLCE2EContainerListTests.cpp
+2
@@ -176,6 +176,8 @@ class WSLCE2EContainerListTests
176 // List containers with json format
177 result = RunWslc(L"container list --all --format json");
178 result.Verify({.Stderr = L"", .ExitCode = 0});
179 + // The payload is emitted on a single compact line.
180 + VERIFY_ARE_EQUAL(1u, result.GetStdoutLines().size());
181 // Parse json and verify we got the expected container information back
182 auto containers = wsl::shared::FromJson<std::vector<ContainerInformation>>(result.Stdout.value().c_str());
183 VERIFY_IS_GREATER_THAN_OR_EQUAL(containers.size(), 1U);
test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
+2 -1
@@ -118,7 +118,8 @@ class WSLCE2EGlobalTests
118 auto result = RunWslc(L"version --format json");
119 result.Verify({.Stderr = L"", .ExitCode = 0});
120 VERIFY_IS_TRUE(result.Stdout.has_value());
121 - const auto root = nlohmann::json::parse(wsl::shared::string::WideToMultiByte(result.Stdout.value()));
121 + // The version payload is emitted as one compact object.
122 + const auto root = VerifyCompactJsonOutput(result);
123 VERIFY_ARE_EQUAL(std::string{WSL_PACKAGE_VERSION}, root["Client"]["Version"].get<std::string>());
124 }
125
test/windows/wslc/e2e/WSLCE2EHelpers.h
+11
@@ -225,6 +225,17 @@ std::pair<wsl::windows::common::RunningWSLCContainer, std::string> StartLocalReg
225 // Tags an image for a registry and returns the full registry image reference (e.g. "127.0.0.1:PORT/debian:latest").
226 std::wstring TagImageForRegistry(const std::wstring& imageName, const std::wstring& registryAddress);
227
228 +// Verifies "--format json" output was emitted as a single compact line and returns the parsed document.
229 +inline nlohmann::json VerifyCompactJsonOutput(const WSLCExecutionResult& result)
230 +{
231 + VERIFY_IS_TRUE(result.Stdout.has_value());
232 +
233 + const auto lines = result.GetStdoutLines();
234 + VERIFY_ARE_EQUAL(1u, lines.size(), L"'--format json' output must be a single line");
235 +
236 + return nlohmann::json::parse(wsl::shared::string::WideToMultiByte(lines[0]));
237 +}
238 +
239 // Verifies that a string is a valid hex ID output.
240 // truncated=true expects 12 hex chars, truncated=false expects 64 hex chars.
241 inline void VerifyIdOutput(const std::wstring& id, bool truncated)
test/windows/wslc/e2e/WSLCE2EImageInspectTests.cpp
+11
@@ -57,6 +57,17 @@ class WSLCE2EImageInspectTests
57 result.Verify({.Stdout = L"[]\r\n", .Stderr = std::format(L"Image '{}' not found.\r\n", InvalidImage.NameAndTag()), .ExitCode = 1});
58 }
59
60 + WSLC_TEST_METHOD(WSLCE2E_Image_Inspect_FormatJson_IsSingleLine)
61 + {
62 + auto result = RunWslc(std::format(L"image inspect --format json {}", DebianImage.NameAndTag()));
63 + result.Verify({.Stderr = L"", .ExitCode = 0});
64 +
65 + const auto document = VerifyCompactJsonOutput(result);
66 + VERIFY_IS_TRUE(document.is_array());
67 + VERIFY_ARE_EQUAL(1u, document.size());
68 + VERIFY_ARE_EQUAL(wsl::shared::string::WideToMultiByte(DebianImage.NameAndTag()), document[0]["RepoTags"][0].get<std::string>());
69 + }
70 +
71 WSLC_TEST_METHOD(WSLCE2E_Image_Inspect_Success)
72 {
73 auto result = RunWslc(std::format(L"image inspect {}", DebianImage.NameAndTag()));
test/windows/wslc/e2e/WSLCE2EInspectTests.cpp
+44
@@ -99,6 +99,50 @@ class WSLCE2EInspectTests
99 result.Verify({.Stdout = L"[]\r\n", .Stderr = std::format(L"Object not found: {}\r\n", DebianImage.NameAndTag()), .ExitCode = 1});
100 }
101
102 + WSLC_TEST_METHOD(WSLCE2E_Inspect_FormatJson_IsSingleLine)
103 + {
104 + // The whole array is emitted on one compact line.
105 + auto result = RunWslc(std::format(L"inspect --format json {}", DebianImage.NameAndTag()));
106 + result.Verify({.Stderr = L"", .ExitCode = 0});
107 +
108 + const auto document = VerifyCompactJsonOutput(result);
109 + VERIFY_IS_TRUE(document.is_array());
110 + VERIFY_ARE_EQUAL(1u, document.size());
111 + VERIFY_ARE_EQUAL(wsl::shared::string::WideToMultiByte(DebianImage.NameAndTag()), document[0]["RepoTags"][0].get<std::string>());
112 +
113 + // The compact rendering must not contain the pretty-printer's indentation.
114 + VERIFY_IS_FALSE(result.StdoutContainsSubstring(L"\n "));
115 + }
116 +
117 + WSLC_TEST_METHOD(WSLCE2E_Inspect_DefaultFormat_IsIndented)
118 + {
119 + // Without --format the array stays indented over several lines.
120 + auto result = RunWslc(std::format(L"inspect {}", DebianImage.NameAndTag()));
121 + result.Verify({.Stderr = L"", .ExitCode = 0});
122 + VERIFY_IS_GREATER_THAN(result.GetStdoutLines().size(), 1u);
123 + }
124 +
125 + WSLC_TEST_METHOD(WSLCE2E_Inspect_InvalidFormatOption)
126 + {
127 + auto result = RunWslc(std::format(L"inspect --format invalid {}", DebianImage.NameAndTag()));
128 + result.Verify({.Stdout = L"", .ExitCode = 1});
129 + VERIFY_IS_TRUE(result.StderrContainsSubstring(
130 + L"Invalid format value: invalid is not a recognized format type. Supported format types are: json."));
131 +
132 + // json is the only rendering the inspect family has; `table` belongs to the list commands.
133 + auto tableResult = RunWslc(std::format(L"inspect --format table {}", DebianImage.NameAndTag()));
134 + tableResult.Verify({.Stdout = L"", .ExitCode = 1});
135 + VERIFY_IS_TRUE(tableResult.StderrContainsSubstring(
136 + L"Invalid format value: table is not a recognized format type. Supported format types are: json."));
137 + }
138 +
139 + WSLC_TEST_METHOD(WSLCE2E_Inspect_FormatJson_ObjectNotFound)
140 + {
141 + // An empty array is already compact, so both formats agree on "[]".
142 + auto result = RunWslc(std::format(L"inspect --format json {}", InvalidImage.NameAndTag()));
143 + result.Verify({.Stdout = L"[]\r\n", .Stderr = std::format(L"Object not found: {}\r\n", InvalidImage.NameAndTag()), .ExitCode = 1});
144 + }
145 +
146 WSLC_TEST_METHOD(WSLCE2E_Inspect_Container_Success)
147 {
148 EnsureContainerDoesNotExist(WslcContainerName);
test/windows/wslc/e2e/WSLCE2ENetworkInspectTests.cpp
+14
@@ -68,6 +68,20 @@ class WSLCE2ENetworkInspectTests
68 VERIFY_ARE_EQUAL("bridge", inspect.Driver);
69 }
70
71 + WSLC_TEST_METHOD(WSLCE2E_Network_Inspect_FormatJson_IsSingleLine)
72 + {
73 + auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName1));
74 + result.Verify({.Stderr = L"", .ExitCode = 0});
75 +
76 + result = RunWslc(std::format(L"network inspect --format json {}", TestNetworkName1));
77 + result.Verify({.Stderr = L"", .ExitCode = 0});
78 +
79 + const auto document = VerifyCompactJsonOutput(result);
80 + VERIFY_IS_TRUE(document.is_array());
81 + VERIFY_ARE_EQUAL(1u, document.size());
82 + VERIFY_ARE_EQUAL(WideToMultiByte(TestNetworkName1), document[0]["Name"].get<std::string>());
83 + }
84 +
85 WSLC_TEST_METHOD(WSLCE2E_Network_InspectMultiple_Success)
86 {
87 auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName1));
test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp
+14
@@ -69,6 +69,20 @@ class WSLCE2EVolumeInspectTests
69 VERIFY_ARE_EQUAL("guest", inspect.Driver);
70 }
71
72 + WSLC_TEST_METHOD(WSLCE2E_Volume_Inspect_FormatJson_IsSingleLine)
73 + {
74 + auto result = RunWslc(std::format(L"volume create {}", TestVolumeName1));
75 + result.Verify({.Stderr = L"", .ExitCode = 0});
76 +
77 + result = RunWslc(std::format(L"volume inspect --format json {}", TestVolumeName1));
78 + result.Verify({.Stderr = L"", .ExitCode = 0});
79 +
80 + const auto document = VerifyCompactJsonOutput(result);
81 + VERIFY_IS_TRUE(document.is_array());
82 + VERIFY_ARE_EQUAL(1u, document.size());
83 + VERIFY_ARE_EQUAL(WideToMultiByte(TestVolumeName1), document[0]["Name"].get<std::string>());
84 + }
85 +
86 WSLC_TEST_METHOD(WSLCE2E_Volume_InspectMultiple_Success)
87 {
88 // Create two volumes to inspect at the same time