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