Match docker's keyset for wslc volume list --format json (#41413)

* Match docker's keyset for wslc volume list --format json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

ggarzia-MSFT committed Aug 24, 2026 at 13:31 UTC 6a19064c08f109a4e280322ae07d043416ae987b
20 files changed +171 -86
src/shared/inc/JsonUtils.h
-20
@@ -185,24 +185,4 @@ struct adl_serializer<wsl::shared::string::MacAddress>
185 }
186 };
187
188 -#ifdef WIN32
189 -template <>
190 -struct adl_serializer<WSLCVolumeInformation>
191 -{
192 - static void to_json(json& j, const WSLCVolumeInformation& volume)
193 - {
194 - j = json{{"Name", std::string(volume.Name)}, {"Driver", std::string(volume.Driver)}};
195 - }
196 -
197 - static void from_json(const json& j, WSLCVolumeInformation& volume)
198 - {
199 - std::string name = j.at("Name").get<std::string>();
200 - std::string driver = j.at("Driver").get<std::string>();
201 -
202 - strncpy_s(volume.Name, sizeof(volume.Name), name.c_str(), _TRUNCATE);
203 - strncpy_s(volume.Driver, sizeof(volume.Driver), driver.c_str(), _TRUNCATE);
204 - }
205 -};
206 -#endif
207 -
188 } // namespace nlohmann
\ No newline at end of file
src/windows/inc/wslc_schema.h
+13
@@ -335,4 +335,17 @@ struct NetworkListEntry
335 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(NetworkListEntry, Id, Name, Driver, Scope, Created, EnableIPv4, EnableIPv6, Internal, Labels);
336 };
337
338 +// The volume properties carried from the session to the CLI for "volume list". Values keep their
339 +// native types; the CLI renders the string output.
340 +struct VolumeListEntry
341 +{
342 + std::string Name;
343 + std::string Driver;
344 + std::string Mountpoint;
345 + std::string Scope;
346 + std::map<std::string, std::string> Labels;
347 +
348 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VolumeListEntry, Name, Driver, Mountpoint, Scope, Labels);
349 +};
350 +
351 } // namespace wsl::windows::common::wslc_schema
src/windows/service/inc/wslc.idl
+1 -1
@@ -758,7 +758,7 @@ interface IWSLCSession : IUnknown
758 // Volume management.
759 HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo);
760 HRESULT DeleteVolume([in] LPCSTR Name);
761 - HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count);
761 + HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] LPSTR* Output);
762 HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output);
763
764 HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken);
src/windows/wslc/core/ExecutionContextData.h
+1 -1
@@ -56,7 +56,7 @@ namespace details {
56 DEFINE_DATA_MAPPING(Containers, std::vector<wsl::windows::wslc::models::ContainerInformation>);
57 DEFINE_DATA_MAPPING(ContainerOptions, wsl::windows::wslc::models::ContainerOptions);
58 DEFINE_DATA_MAPPING(Images, std::vector<wsl::windows::wslc::models::ImageInformation>);
59 - DEFINE_DATA_MAPPING(Volumes, std::vector<WSLCVolumeInformation>);
59 + DEFINE_DATA_MAPPING(Volumes, std::vector<wsl::windows::common::wslc_schema::VolumeListEntry>);
60 DEFINE_DATA_MAPPING(Networks, std::vector<wsl::windows::common::wslc_schema::NetworkListEntry>);
61 DEFINE_DATA_MAPPING(NetworkEndpointOptions, wsl::windows::wslc::models::NetworkEndpointOptions);
62 } // namespace details
src/windows/wslc/services/VolumeModel.h
+17
@@ -33,4 +33,21 @@ struct PruneVolumesResult
33 ULONGLONG SpaceReclaimed{};
34 };
35
36 +// The shape emitted by "volume list --format json"; every value is reported as a string.
37 +struct VolumeOutputInformation
38 +{
39 + std::string Availability;
40 + std::string Driver;
41 + std::string Group;
42 + std::string Labels;
43 + std::string Links;
44 + std::string Mountpoint;
45 + std::string Name;
46 + std::string Scope;
47 + std::string Size;
48 + std::string Status;
49 +
50 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VolumeOutputInformation, Availability, Driver, Group, Labels, Links, Mountpoint, Name, Scope, Size, Status);
51 +};
52 +
53 } // namespace wsl::windows::wslc::models
src/windows/wslc/services/VolumeService.cpp
+5 -12
@@ -60,7 +60,8 @@ void VolumeService::Delete(models::Session& session, const std::string& name)
60 THROW_IF_FAILED(session.Get()->DeleteVolume(name.c_str()));
61 }
62
63 -std::vector<WSLCVolumeInformation> VolumeService::List(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters)
63 +std::vector<wsl::windows::common::wslc_schema::VolumeListEntry> VolumeService::List(
64 + models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters)
65 {
66 std::vector<WSLCFilter> filterEntries;
67 filterEntries.reserve(filters.size());
@@ -69,19 +70,11 @@ std::vector<WSLCVolumeInformation> VolumeService::List(models::Session& session,
70 filterEntries.push_back({.Key = key.c_str(), .Value = value.c_str()});
71 }
72
72 - wil::unique_cotaskmem_array_ptr<WSLCVolumeInformation> rawVolumes;
73 - ULONG count = 0;
73 + wil::unique_cotaskmem_ansistring output;
74 THROW_IF_FAILED(session.Get()->ListVolumes(
75 - filterEntries.empty() ? nullptr : filterEntries.data(), static_cast<ULONG>(filterEntries.size()), &rawVolumes, &count));
76 -
77 - std::vector<WSLCVolumeInformation> volumes;
78 - volumes.reserve(count);
79 - for (auto ptr = rawVolumes.get(), end = rawVolumes.get() + count; ptr != end; ++ptr)
80 - {
81 - volumes.push_back(*ptr);
82 - }
75 + filterEntries.empty() ? nullptr : filterEntries.data(), static_cast<ULONG>(filterEntries.size()), &output));
76
84 - return volumes;
77 + return FromJson<std::vector<wsl::windows::common::wslc_schema::VolumeListEntry>>(output.get());
78 }
79
80 wsl::windows::common::wslc_schema::InspectVolume VolumeService::Inspect(models::Session& session, const std::string& name)
src/windows/wslc/services/VolumeService.h
+2 -1
@@ -24,7 +24,8 @@ struct VolumeService
24 {
25 static WSLCVolumeInformation Create(models::Session& session, const models::CreateVolumeOptions& createOptions);
26 static void Delete(models::Session& session, const std::string& name);
27 - static std::vector<WSLCVolumeInformation> List(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters = {});
27 + static std::vector<wsl::windows::common::wslc_schema::VolumeListEntry> List(
28 + models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters = {});
29 static wsl::windows::common::wslc_schema::InspectVolume Inspect(models::Session& session, const std::string& name);
30 static models::PruneVolumesResult Prune(
31 Terminal& terminal, models::Session& session, bool all, const std::vector<std::pair<std::string, std::string>>& filters = {});
src/windows/wslc/tasks/VolumeTasks.cpp
+36 -1
@@ -33,6 +33,41 @@ namespace wsl::windows::wslc::task {
33
34 constexpr uint32_t c_reclaimedSpacePrecision = 4;
35
36 +namespace {
37 +
38 + // Reported for the fields that only carry a value when volume usage data or swarm cluster
39 + // information is available, neither of which applies here.
40 + constexpr std::string_view c_notAvailable = "N/A";
41 +
42 + // Converts session volume entries into the all-string shape used for "volume list --format json".
43 + VolumeOutputInformation ToVolumeOutput(const wslc_schema::VolumeListEntry& volume)
44 + {
45 + VolumeOutputInformation entry;
46 + entry.Availability = c_notAvailable;
47 + entry.Driver = volume.Driver;
48 + entry.Group = c_notAvailable;
49 + entry.Links = c_notAvailable;
50 + entry.Mountpoint = volume.Mountpoint;
51 + entry.Name = volume.Name;
52 + entry.Scope = volume.Scope;
53 + entry.Size = c_notAvailable;
54 + entry.Status = c_notAvailable;
55 +
56 + for (const auto& [key, value] : volume.Labels)
57 + {
58 + if (!entry.Labels.empty())
59 + {
60 + entry.Labels += ",";
61 + }
62 +
63 + entry.Labels += std::format("{}={}", key, value);
64 + }
65 +
66 + return entry;
67 + }
68 +
69 +} // namespace
70 +
71 static bool TryInspectVolume(Terminal& terminal, Session& session, const std::string& volumeName, std::optional<wslc_schema::InspectVolume>& inspectData)
72 {
73 try
@@ -177,7 +212,7 @@ void ListVolumes(CLIExecutionContext& context)
212 {
213 for (const auto& volume : volumes)
214 {
180 - context.Terminal.Output(L"{}\n", ToJsonW(volume, c_jsonCompactIndent));
215 + context.Terminal.Output(L"{}\n", ToJsonW(ToVolumeOutput(volume), c_jsonCompactIndent));
216 }
217
218 break;
src/windows/wslcsession/IWSLCVolume.h
+3
@@ -40,6 +40,9 @@ public:
40 // The user-specified labels on this volume (excludes the WSLC metadata label).
41 virtual const std::map<std::string, std::string>& Labels() const noexcept = 0;
42
43 + // The path at which the volume is mounted inside the utility VM.
44 + virtual const std::string& Mountpoint() const noexcept = 0;
45 +
46 // The status of the volume as {Code, Message}: S_OK with an empty message when the volume
47 // opened successfully and is usable, otherwise a failure HRESULT and a human-readable reason
48 // (e.g. the backing VHD is missing).
src/windows/wslcsession/WSLCGuestVolume.h
+4
@@ -69,6 +69,10 @@ public:
69 {
70 return m_labels;
71 }
72 + const std::string& Mountpoint() const noexcept override
73 + {
74 + return m_mountpoint;
75 + }
76
77 void Delete() override;
78 std::string Inspect() const override;
src/windows/wslcsession/WSLCSession.cpp
+5 -14
@@ -2776,16 +2776,14 @@ try
2776 }
2777 CATCH_RETURN();
2778
2779 -HRESULT WSLCSession::ListVolumes(const WSLCFilter* Filters, ULONG FiltersCount, WSLCVolumeInformation** Volumes, ULONG* Count)
2779 +HRESULT WSLCSession::ListVolumes(const WSLCFilter* Filters, ULONG FiltersCount, LPSTR* Output)
2780 try
2781 {
2782 WSLCExecutionContext context(this);
2783
2784 - RETURN_HR_IF_NULL(E_POINTER, Volumes);
2785 - RETURN_HR_IF_NULL(E_POINTER, Count);
2784 + RETURN_HR_IF_NULL(E_POINTER, Output);
2785
2787 - *Volumes = nullptr;
2788 - *Count = 0;
2786 + *Output = nullptr;
2787
2788 auto filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Filters, FiltersCount);
2789
@@ -2794,16 +2792,9 @@ try
2792
2793 auto volumeList = m_runtime.Volumes().ListVolumes(std::move(filters));
2794
2797 - if (volumeList.empty())
2798 - {
2799 - return S_OK;
2800 - }
2801 -
2802 - auto output = wil::make_unique_cotaskmem<WSLCVolumeInformation[]>(volumeList.size());
2803 - memcpy(output.get(), volumeList.data(), volumeList.size() * sizeof(WSLCVolumeInformation));
2795 + std::string json = wsl::shared::ToJson(volumeList);
2796 + *Output = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(json.c_str()).release();
2797
2805 - *Count = static_cast<ULONG>(volumeList.size());
2806 - *Volumes = output.release();
2798 return S_OK;
2799 }
2800 CATCH_RETURN();
src/windows/wslcsession/WSLCSession.h
+1 -2
@@ -180,8 +180,7 @@ public:
180 IFACEMETHOD(CreateVolume)(_In_ const WSLCVolumeOptions* Options, _Out_ WSLCVolumeInformation* VolumeInfo) override;
181 IFACEMETHOD(DeleteVolume)(_In_ LPCSTR Name) override;
182 IFACEMETHOD(ListVolumes)
183 - (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ WSLCVolumeInformation** Volumes, _Out_ ULONG* Count)
184 - override;
183 + (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ LPSTR* Output) override;
184 IFACEMETHOD(InspectVolume)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override;
185 IFACEMETHOD(PruneVolumes)
186 (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters,
src/windows/wslcsession/WSLCVhdVolume.h
+4
@@ -77,6 +77,10 @@ public:
77 {
78 return m_labels;
79 }
80 + const std::string& Mountpoint() const noexcept override
81 + {
82 + return m_mountpoint;
83 + }
84
85 std::pair<HRESULT, std::string> Status() const override
86 {
src/windows/wslcsession/WSLCVolumes.cpp
+10 -3
@@ -141,7 +141,7 @@ void WSLCVolumes::DeleteVolume(LPCSTR Name)
141 m_expectedEvents.emplace_back(Name, VolumeEvent::Destroy);
142 }
143
144 -std::vector<WSLCVolumeInformation> WSLCVolumes::ListVolumes(std::map<std::string, std::vector<std::string>>&& Filters) const
144 +std::vector<wsl::windows::common::wslc_schema::VolumeListEntry> WSLCVolumes::ListVolumes(std::map<std::string, std::vector<std::string>>&& Filters) const
145 {
146 // Pull the driver filter out and forward everything else to docker for filtering.
147 // Driver filter is special-cased because our driver concept doesn't map 1:1 to docker's.
@@ -169,7 +169,7 @@ std::vector<WSLCVolumeInformation> WSLCVolumes::ListVolumes(std::map<std::string
169
170 auto lock = m_lock.lock_shared();
171
172 - std::vector<WSLCVolumeInformation> result;
172 + std::vector<wsl::windows::common::wslc_schema::VolumeListEntry> result;
173 result.reserve(dockerVolumeNames.size());
174
175 for (const auto& [name, vol] : m_volumes)
@@ -186,7 +186,14 @@ std::vector<WSLCVolumeInformation> WSLCVolumes::ListVolumes(std::map<std::string
186 continue;
187 }
188
189 - result.push_back(vol->GetVolumeInformation());
189 + wsl::windows::common::wslc_schema::VolumeListEntry entry;
190 + entry.Name = vol->Name();
191 + entry.Driver = vol->Driver();
192 + entry.Mountpoint = vol->Mountpoint();
193 + entry.Scope = WSLCVolumeScope;
194 + entry.Labels = vol->Labels();
195 +
196 + result.push_back(std::move(entry));
197 }
198
199 return result;
src/windows/wslcsession/WSLCVolumes.h
+2 -1
@@ -18,6 +18,7 @@ Abstract:
18 #include "WSLCVolumeMetadata.h"
19 #include "DockerHTTPClient.h"
20 #include "DockerEventTracker.h"
21 +#include <wslc_schema.h>
22
23 namespace wsl::windows::service::wslc {
24
@@ -40,7 +41,7 @@ public:
41
42 void DeleteVolume(_In_ LPCSTR Name);
43
43 - std::vector<WSLCVolumeInformation> ListVolumes(std::map<std::string, std::vector<std::string>>&& Filters) const;
44 + std::vector<wsl::windows::common::wslc_schema::VolumeListEntry> ListVolumes(std::map<std::string, std::vector<std::string>>&& Filters) const;
45
46 struct PruneVolumesResult
47 {
test/windows/WSLCTests.cpp
+20 -24
@@ -654,16 +654,18 @@ class WSLCTests
654 return std::move(deletedImages);
655 }
656
657 - std::set<std::string> ListVolumes(const std::vector<WSLCFilter>& Filters = {})
657 + std::vector<wsl::windows::common::wslc_schema::VolumeListEntry> ListVolumeEntries(const std::vector<WSLCFilter>& Filters = {})
658 {
659 - const WSLCFilter* filtersPtr = Filters.empty() ? nullptr : Filters.data();
660 - const ULONG filtersCount = static_cast<ULONG>(Filters.size());
659 + wil::unique_cotaskmem_ansistring output;
660 + VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(Filters.empty() ? nullptr : Filters.data(), static_cast<ULONG>(Filters.size()), &output));
661
662 - wil::unique_cotaskmem_array_ptr<WSLCVolumeInformation> volumes;
663 - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(filtersPtr, filtersCount, volumes.addressof(), volumes.size_address<ULONG>()));
662 + return wsl::shared::FromJson<std::vector<wsl::windows::common::wslc_schema::VolumeListEntry>>(output.get());
663 + }
664
665 + std::set<std::string> ListVolumes(const std::vector<WSLCFilter>& Filters = {})
666 + {
667 std::set<std::string> names;
666 - for (const auto& v : volumes)
668 + for (const auto& v : ListVolumeEntries(Filters))
669 {
670 names.insert(v.Name);
671 }
@@ -5266,11 +5268,12 @@ class WSLCTests
5268 WSLCVolumeInformation volInfo{};
5269 VERIFY_SUCCEEDED(m_defaultSession->CreateVolume(&vhdOptions, &volInfo));
5270
5269 - wil::unique_cotaskmem_array_ptr<WSLCVolumeInformation> volumes;
5270 - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(nullptr, 0, volumes.addressof(), volumes.size_address<ULONG>()));
5271 + auto volumes = ListVolumeEntries();
5272 VERIFY_ARE_EQUAL(1u, volumes.size());
5272 - VERIFY_ARE_EQUAL(std::string(volumes[0].Name), vhdVolumeName);
5273 - VERIFY_ARE_EQUAL(std::string(volumes[0].Driver), std::string("vhd"));
5273 + VERIFY_ARE_EQUAL(volumes[0].Name, vhdVolumeName);
5274 + VERIFY_ARE_EQUAL(volumes[0].Driver, std::string("vhd"));
5275 + VERIFY_IS_FALSE(volumes[0].Mountpoint.empty());
5276 + VERIFY_ARE_EQUAL(volumes[0].Scope, std::string("local"));
5277
5278 // Verify that a guest volume cannot be created with the same name as an existing vhd volume.
5279 WSLCVolumeOptions duplicateGuestOptions{};
@@ -5292,7 +5295,7 @@ class WSLCTests
5295 duplicateVhdOptions.DriverOptsCount = ARRAYSIZE(driverOpts);
5296 VERIFY_ARE_EQUAL(m_defaultSession->CreateVolume(&duplicateVhdOptions, &volInfo), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
5297
5295 - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(nullptr, 0, volumes.addressof(), volumes.size_address<ULONG>()));
5298 + volumes = ListVolumeEntries();
5299 VERIFY_ARE_EQUAL(2u, volumes.size());
5300
5301 std::map<std::string, std::string> namesToDrivers;
@@ -5335,10 +5338,10 @@ class WSLCTests
5338
5339 // Delete the VHD volume and verify only the guest volume remains.
5340 VERIFY_SUCCEEDED(m_defaultSession->DeleteVolume(vhdVolumeName.c_str()));
5338 - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(nullptr, 0, volumes.addressof(), volumes.size_address<ULONG>()));
5341 + volumes = ListVolumeEntries();
5342 VERIFY_ARE_EQUAL(1u, volumes.size());
5340 - VERIFY_ARE_EQUAL(std::string(volumes[0].Name), guestVolumeName);
5341 - VERIFY_ARE_EQUAL(std::string(volumes[0].Driver), std::string("guest"));
5343 + VERIFY_ARE_EQUAL(volumes[0].Name, guestVolumeName);
5344 + VERIFY_ARE_EQUAL(volumes[0].Driver, std::string("guest"));
5345 }
5346
5347 WSLC_TEST_METHOD(ListVolumesFilters)
@@ -5370,22 +5373,15 @@ class WSLCTests
5373 const WSLCFilter* filtersPtr = filters.empty() ? nullptr : filters.data();
5374 const ULONG filtersCount = static_cast<ULONG>(filters.size());
5375
5373 - wil::unique_cotaskmem_array_ptr<WSLCVolumeInformation> volumes;
5374 - VERIFY_ARE_EQUAL(
5375 - expected, m_defaultSession->ListVolumes(filtersPtr, filtersCount, volumes.addressof(), volumes.size_address<ULONG>()));
5376 + wil::unique_cotaskmem_ansistring output;
5377 + VERIFY_ARE_EQUAL(expected, m_defaultSession->ListVolumes(filtersPtr, filtersCount, &output));
5378 };
5379
5380 auto expectList = [&](const std::vector<std::string>& expected,
5381 const std::vector<WSLCFilter>& filters = {},
5382 const std::source_location& source = std::source_location::current()) {
5381 - const WSLCFilter* filtersPtr = filters.empty() ? nullptr : filters.data();
5382 - const ULONG filtersCount = static_cast<ULONG>(filters.size());
5383 -
5384 - wil::unique_cotaskmem_array_ptr<WSLCVolumeInformation> volumes;
5385 - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(filtersPtr, filtersCount, volumes.addressof(), volumes.size_address<ULONG>()));
5386 -
5383 std::vector<std::string> names;
5388 - for (const auto& v : volumes)
5384 + for (const auto& v : ListVolumeEntries(filters))
5385 {
5386 names.emplace_back(v.Name);
5387 }
test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
+1 -1
@@ -144,7 +144,7 @@ class WSLCCLIExecutionUnitTests
144 }
145 else if (dataType == Data::Volumes)
146 {
147 - std::vector<WSLCVolumeInformation> volumes;
147 + std::vector<wsl::windows::common::wslc_schema::VolumeListEntry> volumes;
148 dataMap.Add<Data::Volumes>(std::move(volumes));
149 handled = true;
150 }
test/windows/wslc/e2e/WSLCE2EHelpers.cpp
+3 -3
@@ -228,7 +228,7 @@ void VerifyVolumeIsListed(const std::wstring& volumeName)
228 {
229 auto result = RunWslc(L"volume list --format json");
230 result.Verify({.Stderr = L"", .ExitCode = 0});
231 - auto volumes = ParseNdjsonOutputAs<WSLCVolumeInformation>(result);
231 + auto volumes = ParseNdjsonOutputAs<VolumeListOutput>(result);
232 for (const auto& vol : volumes)
233 {
234 if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName))
@@ -244,7 +244,7 @@ void VerifyVolumeIsNotListed(const std::wstring& volumeName)
244 {
245 auto result = RunWslc(L"volume list --format json");
246 result.Verify({.Stderr = L"", .ExitCode = 0});
247 - auto volumes = ParseNdjsonOutputAs<WSLCVolumeInformation>(result);
247 + auto volumes = ParseNdjsonOutputAs<VolumeListOutput>(result);
248 for (const auto& vol : volumes)
249 {
250 if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName))
@@ -460,7 +460,7 @@ void EnsureVolumeDoesNotExist(const std::wstring& volumeName)
460 {
461 auto result = RunWslc(L"volume list --format json");
462 result.Verify({.Stderr = L"", .ExitCode = 0});
463 - auto volumes = ParseNdjsonOutputAs<WSLCVolumeInformation>(result);
463 + auto volumes = ParseNdjsonOutputAs<VolumeListOutput>(result);
464 for (const auto& vol : volumes)
465 {
466 if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName))
test/windows/wslc/e2e/WSLCE2EHelpers.h
+16
@@ -78,6 +78,22 @@ struct NetworkListOutput
78 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(NetworkListOutput, CreatedAt, Driver, ID, IPv4, IPv6, Internal, Labels, Name, Scope);
79 };
80
81 +struct VolumeListOutput
82 +{
83 + std::string Availability;
84 + std::string Driver;
85 + std::string Group;
86 + std::string Labels;
87 + std::string Links;
88 + std::string Mountpoint;
89 + std::string Name;
90 + std::string Scope;
91 + std::string Size;
92 + std::string Status;
93 +
94 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VolumeListOutput, Availability, Driver, Group, Labels, Links, Mountpoint, Name, Scope, Size, Status);
95 +};
96 +
97 struct TestImage
98 {
99 std::wstring Name;
test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp
+27 -2
@@ -88,7 +88,7 @@ class WSLCE2EVolumeListTests
88 result = RunWslc(L"volume list --format json");
89 result.Verify({.Stderr = L"", .ExitCode = 0});
90
91 - auto volumes = ParseNdjsonOutputAs<WSLCVolumeInformation>(result);
91 + auto volumes = ParseNdjsonOutputAs<VolumeListOutput>(result);
92 VERIFY_ARE_EQUAL(2U, volumes.size());
93
94 std::vector<std::string> names;
@@ -102,6 +102,31 @@ class WSLCE2EVolumeListTests
102 VERIFY_ARE_NOT_EQUAL(names.end(), std::find(names.begin(), names.end(), WideToMultiByte(TestVolumeName2)));
103 }
104
105 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_ReportsFullFieldSet)
106 + {
107 + auto result = RunWslc(std::format(L"volume create --label env=prod {}", TestVolumeName));
108 + result.Verify({.Stderr = L"", .ExitCode = 0});
109 +
110 + result = RunWslc(L"volume list --format json");
111 + result.Verify({.Stderr = L"", .ExitCode = 0});
112 +
113 + const auto entries = ParseNdjsonOutput(result);
114 + VERIFY_ARE_EQUAL(1u, entries.size());
115 + const auto& volume = entries[0];
116 +
117 + VERIFY_ARE_EQUAL(10u, volume.size());
118 + VERIFY_ARE_EQUAL("N/A", volume["Availability"].get<std::string>());
119 + VERIFY_ARE_EQUAL("guest", volume["Driver"].get<std::string>());
120 + VERIFY_ARE_EQUAL("N/A", volume["Group"].get<std::string>());
121 + VERIFY_ARE_EQUAL("env=prod", volume["Labels"].get<std::string>());
122 + VERIFY_ARE_EQUAL("N/A", volume["Links"].get<std::string>());
123 + VERIFY_IS_FALSE(volume["Mountpoint"].get<std::string>().empty());
124 + VERIFY_ARE_EQUAL(WideToMultiByte(TestVolumeName), volume["Name"].get<std::string>());
125 + VERIFY_ARE_EQUAL("local", volume["Scope"].get<std::string>());
126 + VERIFY_ARE_EQUAL("N/A", volume["Size"].get<std::string>());
127 + VERIFY_ARE_EQUAL("N/A", volume["Status"].get<std::string>());
128 + }
129 +
130 WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_MalformedValue)
131 {
132 const auto result = RunWslc(L"volume list --filter label");
@@ -243,7 +268,7 @@ private:
268 {
269 auto result = RunWslc(std::format(L"volume list --format json {}", filterArgs));
270 result.Verify({.Stderr = L"", .ExitCode = 0});
246 - const auto volumes = ParseNdjsonOutputAs<WSLCVolumeInformation>(result);
271 + const auto volumes = ParseNdjsonOutputAs<VolumeListOutput>(result);
272 std::set<std::string> names;
273 for (const auto& v : volumes)
274 {