winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 66643345d1dd11096bbff949c041053f4c431906
parent 7d4f839752f42e87304205084c654b5ff67cd35a
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Mon, 10 Jul 2023 13:48:45 -0700

configure test command "implemented" (#3414)

Implemented the `configure test` command.  This was just hooking up the command to a workflow that largely mimics the apply and refactoring the progress handler to share code between the two.  The actual "test the state of a configuration set" function in the configuration module was implemented a while ago.
Diffstat:
Msrc/AppInstallerCLICore/Commands/ConfigureCommand.cpp | 2+-
Msrc/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp | 18++++++++++++++++--
Msrc/AppInstallerCLICore/Resources.h | 8+++++++-
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.h | 16+++++++++++++++-
Asrc/AppInstallerCLIE2ETests/ConfigureTestCommand.cs | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/Constants.cs | 3+++
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 21++++++++++++++++++++-
Msrc/AppInstallerSharedLib/Errors.cpp | 4++++
Msrc/AppInstallerSharedLib/Public/AppInstallerErrors.h | 2++
10 files changed, 358 insertions(+), 63 deletions(-)

diff --git a/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp @@ -74,7 +74,7 @@ namespace AppInstaller::CLI OpenConfigurationSet << ShowConfigurationSet << ShowConfigurationSetConflicts << - ConfirmConfigurationProcessing << + ConfirmConfigurationProcessing(true) << ApplyConfigurationSet; } } diff --git a/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp @@ -2,12 +2,18 @@ // Licensed under the MIT License. #include "pch.h" #include "ConfigureTestCommand.h" +#include "Workflows/ConfigurationFlow.h" + +using namespace AppInstaller::CLI::Workflow; namespace AppInstaller::CLI { std::vector<Argument> ConfigureTestCommand::GetArguments() const { - return {}; + return { + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional, true }, + Argument{ Execution::Args::Type::ConfigurationAcceptWarning, Resource::String::ConfigurationAcceptWarningArgumentDescription, ArgumentType::Flag }, + }; } Resource::LocString ConfigureTestCommand::ShortDescription() const @@ -28,6 +34,14 @@ namespace AppInstaller::CLI void ConfigureTestCommand::ExecuteInternal(Execution::Context& context) const { - Command::ExecuteInternal(context); + context << + VerifyIsFullPackage << + VerifyFile(Execution::Args::Type::ConfigurationFile) << + CreateConfigurationProcessor << + OpenConfigurationSet << + ShowConfigurationSet << + ShowConfigurationSetConflicts << + ConfirmConfigurationProcessing(false) << + TestConfigurationSet; } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -61,6 +61,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationEnabledMessage); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationEnablingMessage); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToTest); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidType); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidValue); @@ -70,15 +71,19 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileVersionUnknown); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInDesiredState); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInform); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInitializing); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationLocal); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleNameOnly); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleWithDetails); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotEnabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNoTestRun); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotInDesiredState); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnexpectedTestResult); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitAssertHadNegativeResult); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailed); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedConfigSet); @@ -103,7 +108,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPrompt); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptTest); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -420,6 +420,36 @@ namespace AppInstaller::CLI::Workflow return Resource::String::ConfigurationUnitSkipped(resultCode); } + void OutputUnitRunFailure(Context& context, const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation) + { + std::string description = Utility::Trim(Utility::ConvertToUTF8(resultInformation.Description())); + + AICLI_LOG_LARGE_STRING(Config, Error, << "Configuration unit " << Utility::ConvertToUTF8(unit.UnitName()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] failed with code 0x" + << Logging::SetHRFormat << resultInformation.ResultCode() << " and error message:\n" << description, Utility::ConvertToUTF8(resultInformation.Details())); + + UnitFailedMessageData messageData = GetUnitFailedData(unit, resultInformation); + auto error = context.Reporter.Error(); + error << " "_liv << messageData.Message << std::endl; + + if (messageData.ShowDescription && !description.empty()) + { + constexpr size_t maximumDescriptionLines = 3; + size_t consoleWidth = GetConsoleWidth(); + std::vector<std::string> lines = Utility::SplitIntoLines(description, maximumDescriptionLines + 1); + bool wasLimited = Utility::LimitOutputLines(lines, consoleWidth, maximumDescriptionLines); + + for (const auto& line : lines) + { + error << line << std::endl; + } + + if (wasLimited || !resultInformation.Details().empty()) + { + error << Resource::String::ConfigurationDescriptionWasTruncated << std::endl; + } + } + } + // Coordinates an active progress scope and cancellation of the operation. template<typename OperationT> struct ProgressCancellationUnification @@ -469,21 +499,73 @@ namespace AppInstaller::CLI::Workflow return { std::move(progressScope), operation }; } - // Helper to handle progress callbacks from ApplyConfigurationSetAsync - struct ApplyConfigurationSetProgressOutput + // The base type for progress reporting + template<typename ResultType, typename ProgressType> + struct ConfigurationSetProgressOutputBase { - using ApplyOperation = IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>; + using Operation = IAsyncOperationWithProgress<ResultType, ProgressType>; - ApplyConfigurationSetProgressOutput(Context& context, const ApplyOperation& operation) : + ConfigurationSetProgressOutputBase(Context& context, const Operation& operation) : m_context(context), m_unification({}, operation) { - operation.Progress([&](const IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>& operation, const ConfigurationSetChangeData& data) + operation.Progress([&](const Operation& operation, const ProgressType& data) { Progress(operation, data); }); } - void Progress(const IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>& operation, const ConfigurationSetChangeData& data) + virtual void Progress(const Operation& operation, const ProgressType& data) = 0; + + protected: + void MarkCompleted(const ConfigurationUnit& unit) + { + winrt::guid unitInstance = unit.InstanceIdentifier(); + m_unitsCompleted.insert(unitInstance); + } + + bool UnitHasPreviouslyCompleted(const ConfigurationUnit& unit) + { + winrt::guid unitInstance = unit.InstanceIdentifier(); + return m_unitsCompleted.count(unitInstance) != 0; + } + + // Sends VT progress to the console + void OutputUnitCompletionProgress() + { + // TODO: Change progress reporting to enable separation of spinner and VT progress reporting + // Preferably we want to be able to have: + // 1. Spinner with indefinite progress VT before set application begins + // 2. 1/N VT progress reporting for configuration units while also showing a spinner for the unit itself + } + + void BeginProgress() + { + m_unification.Progress(m_context.Reporter.BeginAsyncProgress(true)); + } + + void EndProgress() + { + m_unification.Reset(); + } + + Context& m_context; + + private: + ProgressCancellationUnification<Operation> m_unification; + std::set<winrt::guid> m_unitsCompleted; + }; + + // Helper to handle progress callbacks from ApplyConfigurationSetAsync + struct ApplyConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase<ApplyConfigurationSetResult, ConfigurationSetChangeData> + { + using Operation = ConfigurationSetProgressOutputBase<ApplyConfigurationSetResult, ConfigurationSetChangeData>::Operation; + + ApplyConfigurationSetProgressOutput(Context& context, const Operation& operation) : + ConfigurationSetProgressOutputBase(context, operation) + { + } + + void Progress(const Operation& operation, const ConfigurationSetChangeData& data) override { auto threadContext = m_context.SetForCurrentThread(); @@ -557,41 +639,17 @@ namespace AppInstaller::CLI::Workflow } else { - std::string description = Utility::Trim(Utility::ConvertToUTF8(resultInformation.Description())); - - AICLI_LOG_LARGE_STRING(Config, Error, << "Configuration unit " << Utility::ConvertToUTF8(unit.UnitName()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] failed with code 0x" - << Logging::SetHRFormat << resultInformation.ResultCode() << " and error message:\n" << description, Utility::ConvertToUTF8(resultInformation.Details())); - - UnitFailedMessageData messageData = GetUnitFailedData(unit, resultInformation); - auto error = m_context.Reporter.Error(); - error << " "_liv << messageData.Message << std::endl; - - if (messageData.ShowDescription && !description.empty()) - { - constexpr size_t maximumDescriptionLines = 3; - size_t consoleWidth = GetConsoleWidth(); - std::vector<std::string> lines = Utility::SplitIntoLines(description, maximumDescriptionLines + 1); - bool wasLimited = Utility::LimitOutputLines(lines, consoleWidth, maximumDescriptionLines); - - for (const auto & line : lines) - { - error << line << std::endl; - } - - if (wasLimited || !resultInformation.Details().empty()) - { - error << Resource::String::ConfigurationDescriptionWasTruncated << std::endl; - } - } + OutputUnitRunFailure(m_context, unit, resultInformation); } - OutputUnitCompletionProgress(); MarkCompleted(unit); + OutputUnitCompletionProgress(); break; case ConfigurationUnitState::Skipped: OutputUnitInProgressIfNeeded(unit); AICLI_LOG(Config, Warning, << "Configuration unit " << Utility::ConvertToUTF8(unit.UnitName()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] was skipped with code 0x" << Logging::SetHRFormat << resultInformation.ResultCode()); m_context.Reporter.Warn() << " "_liv << GetUnitSkippedMessage(resultInformation) << std::endl; + MarkCompleted(unit); OutputUnitCompletionProgress(); break; } @@ -609,41 +667,87 @@ namespace AppInstaller::CLI::Workflow } } - void MarkCompleted(const ConfigurationUnit& unit) - { - winrt::guid unitInstance = unit.InstanceIdentifier(); - m_unitsCompleted.insert(unitInstance); - } + std::set<winrt::guid> m_unitsSeen; + bool m_isFirstProgress = true; + }; - bool UnitHasPreviouslyCompleted(const ConfigurationUnit& unit) + // Helper to handle progress callbacks from TestConfigurationSetAsync + struct TestConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase<TestConfigurationSetResult, TestConfigurationUnitResult> + { + using Operation = ConfigurationSetProgressOutputBase<TestConfigurationSetResult, TestConfigurationUnitResult>::Operation; + + TestConfigurationSetProgressOutput(Context& context, const Operation& operation) : + ConfigurationSetProgressOutputBase(context, operation) { - winrt::guid unitInstance = unit.InstanceIdentifier(); - return m_unitsCompleted.count(unitInstance) != 0; + // Start the spinner for the first unit being tested since we only receive completions + BeginProgress(); } - // Sends VT progress to the console - void OutputUnitCompletionProgress() + void Progress(const Operation& operation, const TestConfigurationUnitResult& data) override { - // TODO: Change progress reporting to enable separation of spinner and VT progress reporting - // Preferably we want to be able to have: - // 1. Spinner with indefinite progress VT before set application begins - // 2. 1/N VT progress reporting for configuration units while also showing a spinner for the unit itself + auto threadContext = m_context.SetForCurrentThread(); + + if (m_isFirstProgress) + { + HandleUnreportedProgress(operation.GetResults()); + } + + HandleUnitProgress(data.Unit(), data.TestResult(), data.ResultInformation()); } - void BeginProgress() + // If no progress has been reported, this function will report the given results + void HandleUnreportedProgress(const TestConfigurationSetResult& result) { - m_unification.Progress(m_context.Reporter.BeginAsyncProgress(true)); + if (m_isFirstProgress) + { + m_isFirstProgress = false; + + for (const TestConfigurationUnitResult& unitResult : result.UnitResults()) + { + HandleUnitProgress(unitResult.Unit(), unitResult.TestResult(), unitResult.ResultInformation()); + } + } } - void EndProgress() + private: + void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationTestResult testResult, const IConfigurationUnitResultInformation& resultInformation) { - m_unification.Reset(); + if (UnitHasPreviouslyCompleted(unit)) + { + return; + } + + EndProgress(); + + { + OutputStream info = m_context.Reporter.Info(); + OutputConfigurationUnitHeader(info, unit, unit.Details() ? unit.Details().UnitName() : unit.UnitName()); + } + + switch (testResult) + { + case ConfigurationTestResult::Failed: + OutputUnitRunFailure(m_context, unit, resultInformation); + break; + case ConfigurationTestResult::Negative: + m_context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationNotInDesiredState << std::endl; + break; + case ConfigurationTestResult::NotRun: + m_context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationNoTestRun << std::endl; + break; + case ConfigurationTestResult::Positive: + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationInDesiredState << std::endl; + break; + default: // ConfigurationTestResult::Unknown + m_context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(testResult)) << std::endl; + break; + } + + MarkCompleted(unit); + OutputUnitCompletionProgress(); + BeginProgress(); } - Context& m_context; - ProgressCancellationUnification<ApplyOperation> m_unification; - std::set<winrt::guid> m_unitsSeen; - std::set<winrt::guid> m_unitsCompleted; bool m_isFirstProgress = true; }; @@ -842,7 +946,7 @@ namespace AppInstaller::CLI::Workflow UNREFERENCED_PARAMETER(context); } - void ConfirmConfigurationProcessing(Execution::Context& context) + void ConfirmConfigurationProcessing::operator()(Execution::Context& context) const { context.Reporter.Warn() << Resource::String::ConfigurationWarning << std::endl; @@ -854,7 +958,8 @@ namespace AppInstaller::CLI::Workflow return; } - if (!context.Reporter.PromptForBoolResponse(Resource::String::ConfigurationWarningPrompt, Reporter::Level::Warning)) + auto promptString = m_isApply ? Resource::String::ConfigurationWarningPromptApply : Resource::String::ConfigurationWarningPromptTest; + if (!context.Reporter.PromptForBoolResponse(promptString, Reporter::Level::Warning)) { AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED); } @@ -888,6 +993,43 @@ namespace AppInstaller::CLI::Workflow } } + void TestConfigurationSet(Execution::Context& context) + { + TestConfigurationSetResult result = nullptr; + ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>(); + + { + auto testOperation = configContext.Processor().TestSetAsync(configContext.Set()); + TestConfigurationSetProgressOutput progress{ context, testOperation }; + + result = testOperation.get(); + progress.HandleUnreportedProgress(result); + } + + switch (result.TestResult()) + { + case ConfigurationTestResult::Failed: + context.Reporter.Error() << Resource::String::ConfigurationFailedToTest << std::endl; + AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_FAILED); + break; + case ConfigurationTestResult::Negative: + context.Reporter.Warn() << Resource::String::ConfigurationNotInDesiredState << std::endl; + context.SetTerminationHR(S_FALSE); + break; + case ConfigurationTestResult::NotRun: + context.Reporter.Warn() << Resource::String::ConfigurationNoTestRun << std::endl; + AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_NOT_RUN); + break; + case ConfigurationTestResult::Positive: + context.Reporter.Info() << Resource::String::ConfigurationInDesiredState << std::endl; + break; + default: // ConfigurationTestResult::Unknown + context.Reporter.Error() << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(result.TestResult())) << std::endl; + AICLI_TERMINATE_CONTEXT(E_FAIL); + break; + } + } + void VerifyIsFullPackage(Execution::Context& context) { if (IsStubPackage()) diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h @@ -33,7 +33,15 @@ namespace AppInstaller::CLI::Workflow // Required Args: None // Inputs: None // Outputs: None - void ConfirmConfigurationProcessing(Execution::Context& context); + struct ConfirmConfigurationProcessing : public WorkflowTask + { + ConfirmConfigurationProcessing(bool isApply) : WorkflowTask("ConfirmConfigurationProcessing"), m_isApply(isApply) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_isApply; + }; // Applies the configuration set, showing progress as it proceeds. // Required Args: None @@ -41,6 +49,12 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void ApplyConfigurationSet(Execution::Context& context); + // Tests the configuration set state, showing progress as it proceeds. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void TestConfigurationSet(Execution::Context& context); + // Verifies the full package is installed. // Required Args: None // Inputs: None diff --git a/src/AppInstallerCLIE2ETests/ConfigureTestCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureTestCommand.cs @@ -0,0 +1,91 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigureTestCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using Microsoft.Management.Infrastructure; + using NUnit.Framework; + + /// <summary> + /// `Configure test` command tests. + /// </summary> + public class ConfigureTestCommand + { + private const string CommandAndAgreements = "configure test --accept-configuration-agreements"; + + /// <summary> + /// Setup done once before all the tests here. + /// </summary> + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("configuration", true); + this.DeleteTxtFiles(); + } + + /// <summary> + /// Teardown done once after all the tests here. + /// </summary> + [OneTimeTearDown] + public void OneTimeTeardown() + { + this.DeleteTxtFiles(); + } + + /// <summary> + /// Checks for a resource not in the desired state. + /// </summary> + [Test] + public void ConfigureTest_NotInDesiredState() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + this.DeleteTxtFiles(); + + var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); + } + + /// <summary> + /// Checks for a resource in a desired state. + /// </summary> + [Test] + public void ConfigureTest_InDesiredState() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + this.DeleteTxtFiles(); + + // Set up the expected state + File.WriteAllText(TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"), "Contents!"); + + var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("System is in the described configuration state.")); + } + + /// <summary> + /// One resource fails. + /// </summary> + [Test] + public void ConfigureTest_TestFailure() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\IndependentResources_OneFailure.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_TEST_FAILED, result.ExitCode); + Assert.True(result.StdOut.Contains("Some of the configuration units failed while testing their state.")); + Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); + } + + private void DeleteTxtFiles() + { + // Delete all .txt files in the test directory; they are placed there by the tests + foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) + { + File.Delete(file); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -126,6 +126,7 @@ namespace AppInstallerCLIE2ETests public class ErrorCode { public const int S_OK = 0; + public const int S_FALSE = 1; public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003); public const int ERROR_NO_RANGES_PROCESSED = unchecked((int)0x80070138); @@ -281,6 +282,8 @@ namespace AppInstallerCLIE2ETests public const int CONFIG_ERROR_SET_DEPENDENCY_CYCLE = unchecked((int)0x8A15C00C); public const int CONFIG_ERROR_INVALID_FIELD_VALUE = unchecked((int)0x8A15C00D); public const int CONFIG_ERROR_MISSING_FIELD = unchecked((int)0x8A15C00E); + public const int CONFIG_ERROR_TEST_FAILED = unchecked((int)0x8A15C00F); + public const int CONFIG_ERROR_TEST_NOT_RUN = unchecked((int)0x8A15C010); public const int CONFIG_ERROR_UNIT_NOT_INSTALLED = unchecked((int)0x8A15C101); public const int CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY = unchecked((int)0x8A15C102); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1792,7 +1792,7 @@ Please specify one of them using the --source option to proceed.</value> <value>You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services.</value> <comment>Legal approved. Do not change without approval.</comment> </data> - <data name="ConfigurationWarningPrompt" xml:space="preserve"> + <data name="ConfigurationWarningPromptApply" xml:space="preserve"> <value>Have you reviewed the configuration and would you like to proceed applying it to the system?</value> <comment>PM approved.</comment> </data> @@ -1980,4 +1980,23 @@ Please specify one of them using the --source option to proceed.</value> <value>Failed to refresh PATH variable for process. Subsequent installs that depend on changes to the PATH variable may fail.</value> <comment>{Locked="PATH"}</comment> </data> + <data name="ConfigurationFailedToTest" xml:space="preserve"> + <value>Some of the configuration units failed while testing their state.</value> + </data> + <data name="ConfigurationInDesiredState" xml:space="preserve"> + <value>System is in the described configuration state.</value> + </data> + <data name="ConfigurationNoTestRun" xml:space="preserve"> + <value>Configuration state was not tested.</value> + </data> + <data name="ConfigurationNotInDesiredState" xml:space="preserve"> + <value>System is not in the described configuration state.</value> + </data> + <data name="ConfigurationUnexpectedTestResult" xml:space="preserve"> + <value>Unexpected test result: {0}</value> + <comment>{Locked="{0}"} Error message. {0} will be replaced with the unexpected value (a number).</comment> + </data> + <data name="ConfigurationWarningPromptTest" xml:space="preserve"> + <value>Have you reviewed the configuration and would you like to proceed verifying it against the system?</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp @@ -312,6 +312,10 @@ namespace AppInstaller return "The configuration has an invalid field value."; case WINGET_CONFIG_ERROR_MISSING_FIELD: return "The configuration is missing a field."; + case WINGET_CONFIG_ERROR_TEST_FAILED: + return "Some of the configuration units failed while testing their state."; + case WINGET_CONFIG_ERROR_TEST_NOT_RUN: + return "Configuration state was not tested."; // Configuration Processor Errors case WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED: diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -169,6 +169,8 @@ #define WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE ((HRESULT)0x8A15C00C) #define WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE ((HRESULT)0x8A15C00D) #define WINGET_CONFIG_ERROR_MISSING_FIELD ((HRESULT)0x8A15C00E) +#define WINGET_CONFIG_ERROR_TEST_FAILED ((HRESULT)0x8A15C00F) +#define WINGET_CONFIG_ERROR_TEST_NOT_RUN ((HRESULT)0x8A15C010) // Configuration Processor Errors #define WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED ((HRESULT)0x8A15C101)