diff --git a/src/shared/inc/JsonUtils.h b/src/shared/inc/JsonUtils.h index e1c7bc30b..60639e1ba 100644 --- a/src/shared/inc/JsonUtils.h +++ b/src/shared/inc/JsonUtils.h @@ -185,24 +185,4 @@ struct adl_serializer } }; -#ifdef WIN32 -template <> -struct adl_serializer -{ - static void to_json(json& j, const WSLCVolumeInformation& volume) - { - j = json{{"Name", std::string(volume.Name)}, {"Driver", std::string(volume.Driver)}}; - } - - static void from_json(const json& j, WSLCVolumeInformation& volume) - { - std::string name = j.at("Name").get(); - std::string driver = j.at("Driver").get(); - - strncpy_s(volume.Name, sizeof(volume.Name), name.c_str(), _TRUNCATE); - strncpy_s(volume.Driver, sizeof(volume.Driver), driver.c_str(), _TRUNCATE); - } -}; -#endif - } // namespace nlohmann \ No newline at end of file diff --git a/src/windows/inc/wslc_schema.h b/src/windows/inc/wslc_schema.h index 1153ad05a..0df438a76 100644 --- a/src/windows/inc/wslc_schema.h +++ b/src/windows/inc/wslc_schema.h @@ -335,4 +335,17 @@ struct NetworkListEntry NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(NetworkListEntry, Id, Name, Driver, Scope, Created, EnableIPv4, EnableIPv6, Internal, Labels); }; +// The volume properties carried from the session to the CLI for "volume list". Values keep their +// native types; the CLI renders the string output. +struct VolumeListEntry +{ + std::string Name; + std::string Driver; + std::string Mountpoint; + std::string Scope; + std::map Labels; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VolumeListEntry, Name, Driver, Mountpoint, Scope, Labels); +}; + } // namespace wsl::windows::common::wslc_schema diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 1bd1fce68..da410a415 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -758,7 +758,7 @@ interface IWSLCSession : IUnknown // Volume management. HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo); HRESULT DeleteVolume([in] LPCSTR Name); - HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count); + HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] LPSTR* Output); HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output); HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken); diff --git a/src/windows/wslc/core/ExecutionContextData.h b/src/windows/wslc/core/ExecutionContextData.h index 247a3feb9..09d952929 100644 --- a/src/windows/wslc/core/ExecutionContextData.h +++ b/src/windows/wslc/core/ExecutionContextData.h @@ -56,7 +56,7 @@ namespace details { DEFINE_DATA_MAPPING(Containers, std::vector); DEFINE_DATA_MAPPING(ContainerOptions, wsl::windows::wslc::models::ContainerOptions); DEFINE_DATA_MAPPING(Images, std::vector); - DEFINE_DATA_MAPPING(Volumes, std::vector); + DEFINE_DATA_MAPPING(Volumes, std::vector); DEFINE_DATA_MAPPING(Networks, std::vector); DEFINE_DATA_MAPPING(NetworkEndpointOptions, wsl::windows::wslc::models::NetworkEndpointOptions); } // namespace details diff --git a/src/windows/wslc/services/VolumeModel.h b/src/windows/wslc/services/VolumeModel.h index 6aa4adbf8..39270df8f 100644 --- a/src/windows/wslc/services/VolumeModel.h +++ b/src/windows/wslc/services/VolumeModel.h @@ -33,4 +33,21 @@ struct PruneVolumesResult ULONGLONG SpaceReclaimed{}; }; +// The shape emitted by "volume list --format json"; every value is reported as a string. +struct VolumeOutputInformation +{ + std::string Availability; + std::string Driver; + std::string Group; + std::string Labels; + std::string Links; + std::string Mountpoint; + std::string Name; + std::string Scope; + std::string Size; + std::string Status; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VolumeOutputInformation, Availability, Driver, Group, Labels, Links, Mountpoint, Name, Scope, Size, Status); +}; + } // namespace wsl::windows::wslc::models diff --git a/src/windows/wslc/services/VolumeService.cpp b/src/windows/wslc/services/VolumeService.cpp index 67993f0a1..67174b0e5 100644 --- a/src/windows/wslc/services/VolumeService.cpp +++ b/src/windows/wslc/services/VolumeService.cpp @@ -60,7 +60,8 @@ void VolumeService::Delete(models::Session& session, const std::string& name) THROW_IF_FAILED(session.Get()->DeleteVolume(name.c_str())); } -std::vector VolumeService::List(models::Session& session, const std::vector>& filters) +std::vector VolumeService::List( + models::Session& session, const std::vector>& filters) { std::vector filterEntries; filterEntries.reserve(filters.size()); @@ -69,19 +70,11 @@ std::vector VolumeService::List(models::Session& session, filterEntries.push_back({.Key = key.c_str(), .Value = value.c_str()}); } - wil::unique_cotaskmem_array_ptr rawVolumes; - ULONG count = 0; + wil::unique_cotaskmem_ansistring output; THROW_IF_FAILED(session.Get()->ListVolumes( - filterEntries.empty() ? nullptr : filterEntries.data(), static_cast(filterEntries.size()), &rawVolumes, &count)); - - std::vector volumes; - volumes.reserve(count); - for (auto ptr = rawVolumes.get(), end = rawVolumes.get() + count; ptr != end; ++ptr) - { - volumes.push_back(*ptr); - } + filterEntries.empty() ? nullptr : filterEntries.data(), static_cast(filterEntries.size()), &output)); - return volumes; + return FromJson>(output.get()); } wsl::windows::common::wslc_schema::InspectVolume VolumeService::Inspect(models::Session& session, const std::string& name) diff --git a/src/windows/wslc/services/VolumeService.h b/src/windows/wslc/services/VolumeService.h index a21a2da4c..ff5ba7ed6 100644 --- a/src/windows/wslc/services/VolumeService.h +++ b/src/windows/wslc/services/VolumeService.h @@ -24,7 +24,8 @@ struct VolumeService { static WSLCVolumeInformation Create(models::Session& session, const models::CreateVolumeOptions& createOptions); static void Delete(models::Session& session, const std::string& name); - static std::vector List(models::Session& session, const std::vector>& filters = {}); + static std::vector List( + models::Session& session, const std::vector>& filters = {}); static wsl::windows::common::wslc_schema::InspectVolume Inspect(models::Session& session, const std::string& name); static models::PruneVolumesResult Prune( Terminal& terminal, models::Session& session, bool all, const std::vector>& filters = {}); diff --git a/src/windows/wslc/tasks/VolumeTasks.cpp b/src/windows/wslc/tasks/VolumeTasks.cpp index 552363325..fc548def6 100644 --- a/src/windows/wslc/tasks/VolumeTasks.cpp +++ b/src/windows/wslc/tasks/VolumeTasks.cpp @@ -33,6 +33,41 @@ namespace wsl::windows::wslc::task { constexpr uint32_t c_reclaimedSpacePrecision = 4; +namespace { + + // Reported for the fields that only carry a value when volume usage data or swarm cluster + // information is available, neither of which applies here. + constexpr std::string_view c_notAvailable = "N/A"; + + // Converts session volume entries into the all-string shape used for "volume list --format json". + VolumeOutputInformation ToVolumeOutput(const wslc_schema::VolumeListEntry& volume) + { + VolumeOutputInformation entry; + entry.Availability = c_notAvailable; + entry.Driver = volume.Driver; + entry.Group = c_notAvailable; + entry.Links = c_notAvailable; + entry.Mountpoint = volume.Mountpoint; + entry.Name = volume.Name; + entry.Scope = volume.Scope; + entry.Size = c_notAvailable; + entry.Status = c_notAvailable; + + for (const auto& [key, value] : volume.Labels) + { + if (!entry.Labels.empty()) + { + entry.Labels += ","; + } + + entry.Labels += std::format("{}={}", key, value); + } + + return entry; + } + +} // namespace + static bool TryInspectVolume(Terminal& terminal, Session& session, const std::string& volumeName, std::optional& inspectData) { try @@ -177,7 +212,7 @@ void ListVolumes(CLIExecutionContext& context) { for (const auto& volume : volumes) { - context.Terminal.Output(L"{}\n", ToJsonW(volume, c_jsonCompactIndent)); + context.Terminal.Output(L"{}\n", ToJsonW(ToVolumeOutput(volume), c_jsonCompactIndent)); } break; diff --git a/src/windows/wslcsession/IWSLCVolume.h b/src/windows/wslcsession/IWSLCVolume.h index afb9e1e49..9f20a4725 100644 --- a/src/windows/wslcsession/IWSLCVolume.h +++ b/src/windows/wslcsession/IWSLCVolume.h @@ -40,6 +40,9 @@ class IWSLCVolume // The user-specified labels on this volume (excludes the WSLC metadata label). virtual const std::map& Labels() const noexcept = 0; + // The path at which the volume is mounted inside the utility VM. + virtual const std::string& Mountpoint() const noexcept = 0; + // The status of the volume as {Code, Message}: S_OK with an empty message when the volume // opened successfully and is usable, otherwise a failure HRESULT and a human-readable reason // (e.g. the backing VHD is missing). diff --git a/src/windows/wslcsession/WSLCGuestVolume.h b/src/windows/wslcsession/WSLCGuestVolume.h index 4ac78ad5c..d4057c38c 100644 --- a/src/windows/wslcsession/WSLCGuestVolume.h +++ b/src/windows/wslcsession/WSLCGuestVolume.h @@ -69,6 +69,10 @@ class WSLCGuestVolumeImpl : public IWSLCVolume { return m_labels; } + const std::string& Mountpoint() const noexcept override + { + return m_mountpoint; + } void Delete() override; std::string Inspect() const override; diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 31fff7997..0ebc996af 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2776,16 +2776,14 @@ try } CATCH_RETURN(); -HRESULT WSLCSession::ListVolumes(const WSLCFilter* Filters, ULONG FiltersCount, WSLCVolumeInformation** Volumes, ULONG* Count) +HRESULT WSLCSession::ListVolumes(const WSLCFilter* Filters, ULONG FiltersCount, LPSTR* Output) try { WSLCExecutionContext context(this); - RETURN_HR_IF_NULL(E_POINTER, Volumes); - RETURN_HR_IF_NULL(E_POINTER, Count); + RETURN_HR_IF_NULL(E_POINTER, Output); - *Volumes = nullptr; - *Count = 0; + *Output = nullptr; auto filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Filters, FiltersCount); @@ -2794,16 +2792,9 @@ try auto volumeList = m_runtime.Volumes().ListVolumes(std::move(filters)); - if (volumeList.empty()) - { - return S_OK; - } - - auto output = wil::make_unique_cotaskmem(volumeList.size()); - memcpy(output.get(), volumeList.data(), volumeList.size() * sizeof(WSLCVolumeInformation)); + std::string json = wsl::shared::ToJson(volumeList); + *Output = wil::make_unique_ansistring(json.c_str()).release(); - *Count = static_cast(volumeList.size()); - *Volumes = output.release(); return S_OK; } CATCH_RETURN(); diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index 17c656c19..ae1bb5e34 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -180,8 +180,7 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession IFACEMETHOD(CreateVolume)(_In_ const WSLCVolumeOptions* Options, _Out_ WSLCVolumeInformation* VolumeInfo) override; IFACEMETHOD(DeleteVolume)(_In_ LPCSTR Name) override; IFACEMETHOD(ListVolumes) - (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ WSLCVolumeInformation** Volumes, _Out_ ULONG* Count) - override; + (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ LPSTR* Output) override; IFACEMETHOD(InspectVolume)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override; IFACEMETHOD(PruneVolumes) (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, diff --git a/src/windows/wslcsession/WSLCVhdVolume.h b/src/windows/wslcsession/WSLCVhdVolume.h index 6aff4cf8e..78165d8c4 100644 --- a/src/windows/wslcsession/WSLCVhdVolume.h +++ b/src/windows/wslcsession/WSLCVhdVolume.h @@ -77,6 +77,10 @@ class WSLCVhdVolumeImpl : public IWSLCVolume { return m_labels; } + const std::string& Mountpoint() const noexcept override + { + return m_mountpoint; + } std::pair Status() const override { diff --git a/src/windows/wslcsession/WSLCVolumes.cpp b/src/windows/wslcsession/WSLCVolumes.cpp index 143bca123..5c3c47913 100644 --- a/src/windows/wslcsession/WSLCVolumes.cpp +++ b/src/windows/wslcsession/WSLCVolumes.cpp @@ -141,7 +141,7 @@ void WSLCVolumes::DeleteVolume(LPCSTR Name) m_expectedEvents.emplace_back(Name, VolumeEvent::Destroy); } -std::vector WSLCVolumes::ListVolumes(std::map>&& Filters) const +std::vector WSLCVolumes::ListVolumes(std::map>&& Filters) const { // Pull the driver filter out and forward everything else to docker for filtering. // Driver filter is special-cased because our driver concept doesn't map 1:1 to docker's. @@ -169,7 +169,7 @@ std::vector WSLCVolumes::ListVolumes(std::map result; + std::vector result; result.reserve(dockerVolumeNames.size()); for (const auto& [name, vol] : m_volumes) @@ -186,7 +186,14 @@ std::vector WSLCVolumes::ListVolumes(std::mapGetVolumeInformation()); + wsl::windows::common::wslc_schema::VolumeListEntry entry; + entry.Name = vol->Name(); + entry.Driver = vol->Driver(); + entry.Mountpoint = vol->Mountpoint(); + entry.Scope = WSLCVolumeScope; + entry.Labels = vol->Labels(); + + result.push_back(std::move(entry)); } return result; diff --git a/src/windows/wslcsession/WSLCVolumes.h b/src/windows/wslcsession/WSLCVolumes.h index 893a8c3b3..24a078a08 100644 --- a/src/windows/wslcsession/WSLCVolumes.h +++ b/src/windows/wslcsession/WSLCVolumes.h @@ -18,6 +18,7 @@ Module Name: #include "WSLCVolumeMetadata.h" #include "DockerHTTPClient.h" #include "DockerEventTracker.h" +#include namespace wsl::windows::service::wslc { @@ -40,7 +41,7 @@ class WSLCVolumes void DeleteVolume(_In_ LPCSTR Name); - std::vector ListVolumes(std::map>&& Filters) const; + std::vector ListVolumes(std::map>&& Filters) const; struct PruneVolumesResult { diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 4e0ea24d1..05740f9be 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -654,16 +654,18 @@ class WSLCTests return std::move(deletedImages); } - std::set ListVolumes(const std::vector& Filters = {}) + std::vector ListVolumeEntries(const std::vector& Filters = {}) { - const WSLCFilter* filtersPtr = Filters.empty() ? nullptr : Filters.data(); - const ULONG filtersCount = static_cast(Filters.size()); + wil::unique_cotaskmem_ansistring output; + VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(Filters.empty() ? nullptr : Filters.data(), static_cast(Filters.size()), &output)); - wil::unique_cotaskmem_array_ptr volumes; - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(filtersPtr, filtersCount, volumes.addressof(), volumes.size_address())); + return wsl::shared::FromJson>(output.get()); + } + std::set ListVolumes(const std::vector& Filters = {}) + { std::set names; - for (const auto& v : volumes) + for (const auto& v : ListVolumeEntries(Filters)) { names.insert(v.Name); } @@ -5062,11 +5064,12 @@ class WSLCTests WSLCVolumeInformation volInfo{}; VERIFY_SUCCEEDED(m_defaultSession->CreateVolume(&vhdOptions, &volInfo)); - wil::unique_cotaskmem_array_ptr volumes; - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(nullptr, 0, volumes.addressof(), volumes.size_address())); + auto volumes = ListVolumeEntries(); VERIFY_ARE_EQUAL(1u, volumes.size()); - VERIFY_ARE_EQUAL(std::string(volumes[0].Name), vhdVolumeName); - VERIFY_ARE_EQUAL(std::string(volumes[0].Driver), std::string("vhd")); + VERIFY_ARE_EQUAL(volumes[0].Name, vhdVolumeName); + VERIFY_ARE_EQUAL(volumes[0].Driver, std::string("vhd")); + VERIFY_IS_FALSE(volumes[0].Mountpoint.empty()); + VERIFY_ARE_EQUAL(volumes[0].Scope, std::string("local")); // Verify that a guest volume cannot be created with the same name as an existing vhd volume. WSLCVolumeOptions duplicateGuestOptions{}; @@ -5088,7 +5091,7 @@ class WSLCTests duplicateVhdOptions.DriverOptsCount = ARRAYSIZE(driverOpts); VERIFY_ARE_EQUAL(m_defaultSession->CreateVolume(&duplicateVhdOptions, &volInfo), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(nullptr, 0, volumes.addressof(), volumes.size_address())); + volumes = ListVolumeEntries(); VERIFY_ARE_EQUAL(2u, volumes.size()); std::map namesToDrivers; @@ -5131,10 +5134,10 @@ class WSLCTests // Delete the VHD volume and verify only the guest volume remains. VERIFY_SUCCEEDED(m_defaultSession->DeleteVolume(vhdVolumeName.c_str())); - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(nullptr, 0, volumes.addressof(), volumes.size_address())); + volumes = ListVolumeEntries(); VERIFY_ARE_EQUAL(1u, volumes.size()); - VERIFY_ARE_EQUAL(std::string(volumes[0].Name), guestVolumeName); - VERIFY_ARE_EQUAL(std::string(volumes[0].Driver), std::string("guest")); + VERIFY_ARE_EQUAL(volumes[0].Name, guestVolumeName); + VERIFY_ARE_EQUAL(volumes[0].Driver, std::string("guest")); } WSLC_TEST_METHOD(ListVolumesFilters) @@ -5166,22 +5169,15 @@ class WSLCTests const WSLCFilter* filtersPtr = filters.empty() ? nullptr : filters.data(); const ULONG filtersCount = static_cast(filters.size()); - wil::unique_cotaskmem_array_ptr volumes; - VERIFY_ARE_EQUAL( - expected, m_defaultSession->ListVolumes(filtersPtr, filtersCount, volumes.addressof(), volumes.size_address())); + wil::unique_cotaskmem_ansistring output; + VERIFY_ARE_EQUAL(expected, m_defaultSession->ListVolumes(filtersPtr, filtersCount, &output)); }; auto expectList = [&](const std::vector& expected, const std::vector& filters = {}, const std::source_location& source = std::source_location::current()) { - const WSLCFilter* filtersPtr = filters.empty() ? nullptr : filters.data(); - const ULONG filtersCount = static_cast(filters.size()); - - wil::unique_cotaskmem_array_ptr volumes; - VERIFY_SUCCEEDED(m_defaultSession->ListVolumes(filtersPtr, filtersCount, volumes.addressof(), volumes.size_address())); - std::vector names; - for (const auto& v : volumes) + for (const auto& v : ListVolumeEntries(filters)) { names.emplace_back(v.Name); } diff --git a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp index d45c7c525..2ba79c007 100644 --- a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp +++ b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp @@ -144,7 +144,7 @@ class WSLCCLIExecutionUnitTests } else if (dataType == Data::Volumes) { - std::vector volumes; + std::vector volumes; dataMap.Add(std::move(volumes)); handled = true; } diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp index 8cd2d4daf..aa560acf4 100644 --- a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp +++ b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp @@ -228,7 +228,7 @@ void VerifyVolumeIsListed(const std::wstring& volumeName) { auto result = RunWslc(L"volume list --format json"); result.Verify({.Stderr = L"", .ExitCode = 0}); - auto volumes = ParseNdjsonOutputAs(result); + auto volumes = ParseNdjsonOutputAs(result); for (const auto& vol : volumes) { if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName)) @@ -244,7 +244,7 @@ void VerifyVolumeIsNotListed(const std::wstring& volumeName) { auto result = RunWslc(L"volume list --format json"); result.Verify({.Stderr = L"", .ExitCode = 0}); - auto volumes = ParseNdjsonOutputAs(result); + auto volumes = ParseNdjsonOutputAs(result); for (const auto& vol : volumes) { if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName)) @@ -460,7 +460,7 @@ void EnsureVolumeDoesNotExist(const std::wstring& volumeName) { auto result = RunWslc(L"volume list --format json"); result.Verify({.Stderr = L"", .ExitCode = 0}); - auto volumes = ParseNdjsonOutputAs(result); + auto volumes = ParseNdjsonOutputAs(result); for (const auto& vol : volumes) { if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName)) diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.h b/test/windows/wslc/e2e/WSLCE2EHelpers.h index 421cb3491..065b7cab0 100644 --- a/test/windows/wslc/e2e/WSLCE2EHelpers.h +++ b/test/windows/wslc/e2e/WSLCE2EHelpers.h @@ -78,6 +78,22 @@ struct NetworkListOutput NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(NetworkListOutput, CreatedAt, Driver, ID, IPv4, IPv6, Internal, Labels, Name, Scope); }; +struct VolumeListOutput +{ + std::string Availability; + std::string Driver; + std::string Group; + std::string Labels; + std::string Links; + std::string Mountpoint; + std::string Name; + std::string Scope; + std::string Size; + std::string Status; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VolumeListOutput, Availability, Driver, Group, Labels, Links, Mountpoint, Name, Scope, Size, Status); +}; + struct TestImage { std::wstring Name; diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp index bc0fcadc0..918195d06 100644 --- a/test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp @@ -88,7 +88,7 @@ class WSLCE2EVolumeListTests result = RunWslc(L"volume list --format json"); result.Verify({.Stderr = L"", .ExitCode = 0}); - auto volumes = ParseNdjsonOutputAs(result); + auto volumes = ParseNdjsonOutputAs(result); VERIFY_ARE_EQUAL(2U, volumes.size()); std::vector names; @@ -102,6 +102,31 @@ class WSLCE2EVolumeListTests VERIFY_ARE_NOT_EQUAL(names.end(), std::find(names.begin(), names.end(), WideToMultiByte(TestVolumeName2))); } + WSLC_TEST_METHOD(WSLCE2E_Volume_List_ReportsFullFieldSet) + { + auto result = RunWslc(std::format(L"volume create --label env=prod {}", TestVolumeName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(L"volume list --format json"); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto entries = ParseNdjsonOutput(result); + VERIFY_ARE_EQUAL(1u, entries.size()); + const auto& volume = entries[0]; + + VERIFY_ARE_EQUAL(10u, volume.size()); + VERIFY_ARE_EQUAL("N/A", volume["Availability"].get()); + VERIFY_ARE_EQUAL("guest", volume["Driver"].get()); + VERIFY_ARE_EQUAL("N/A", volume["Group"].get()); + VERIFY_ARE_EQUAL("env=prod", volume["Labels"].get()); + VERIFY_ARE_EQUAL("N/A", volume["Links"].get()); + VERIFY_IS_FALSE(volume["Mountpoint"].get().empty()); + VERIFY_ARE_EQUAL(WideToMultiByte(TestVolumeName), volume["Name"].get()); + VERIFY_ARE_EQUAL("local", volume["Scope"].get()); + VERIFY_ARE_EQUAL("N/A", volume["Size"].get()); + VERIFY_ARE_EQUAL("N/A", volume["Status"].get()); + } + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_MalformedValue) { const auto result = RunWslc(L"volume list --filter label"); @@ -243,7 +268,7 @@ class WSLCE2EVolumeListTests { auto result = RunWslc(std::format(L"volume list --format json {}", filterArgs)); result.Verify({.Stderr = L"", .ExitCode = 0}); - const auto volumes = ParseNdjsonOutputAs(result); + const auto volumes = ParseNdjsonOutputAs(result); std::set names; for (const auto& v : volumes) {