Skip to content

Commit a1cd9ec

Browse files
authored
Merge pull request #736 from SteveL-MSFT/assert-dependson
Fix `Assertion` resource to fail when test fails
2 parents 02906fc + fa5ecf5 commit a1cd9ec

File tree

9 files changed

+88
-17
lines changed

9 files changed

+88
-17
lines changed

dsc/assertion.dsc.resource.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"pass-through",
1212
"config",
1313
"--as-group",
14+
"--as-assert",
1415
"test",
1516
"--as-get",
1617
{
@@ -26,6 +27,7 @@
2627
"pass-through",
2728
"config",
2829
"--as-group",
30+
"--as-assert",
2931
"test",
3032
{
3133
"jsonInputArg": "--input",
@@ -42,6 +44,7 @@
4244
"pass-through",
4345
"config",
4446
"--as-group",
47+
"--as-assert",
4548
"test",
4649
"--as-config",
4750
{
@@ -59,7 +62,8 @@
5962
"4": "Invalid input format",
6063
"5": "Resource instance failed schema validation",
6164
"6": "Command cancelled",
62-
"7": "Resource not found"
65+
"7": "Resource not found",
66+
"8": "Assertion failed"
6367
},
6468
"validate": {
6569
"executable": "dsc",

dsc/examples/assertion.dsc.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ resources:
2323
keyPath: HKLM\Software\Microsoft\Windows NT\CurrentVersion
2424
valueName: SystemRoot
2525
valueData:
26-
# this is deliberately set to z: drive so that the assertion fails
27-
String: Z:\Windows
26+
# this is deliberately set to L: drive so that the assertion fails
27+
String: L:\Windows

dsc/locales/en-us.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ testInputEmpty = "Expected input is required"
7373
[subcommand]
7474
actualStateNotObject = "actual_state is not an object"
7575
unexpectedTestResult = "Unexpected Group TestResult"
76+
assertionFailed = "Assertion failed for resource '%{resource_type}'"
7677
message = "message"
7778
currentDirectory = "current directory"
7879
noParameters = "No parameters specified"

dsc/src/args.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub enum SubCommand {
5858
// Used to inform when DSC is used as a group resource to modify it's output
5959
#[clap(long, hide = true)]
6060
as_group: bool,
61+
#[clap(long, hide = true)]
62+
as_assert: bool,
6163
// Used to inform when DSC is used as a include group resource
6264
#[clap(long, hide = true)]
6365
as_include: bool,

dsc/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,19 @@ fn main() {
4949
let mut cmd = Args::command();
5050
generate(shell, &mut cmd, "dsc", &mut io::stdout());
5151
},
52-
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_include } => {
52+
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => {
5353
if let Some(file_name) = parameters_file {
5454
info!("{}: {file_name}", t!("main.readingParametersFile"));
5555
match std::fs::read_to_string(&file_name) {
56-
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_include, progress_format),
56+
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format),
5757
Err(err) => {
5858
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
5959
exit(util::EXIT_INVALID_INPUT);
6060
}
6161
}
6262
}
6363
else {
64-
subcommand::config(&subcommand, &parameters, system_root.as_ref(), &as_group, &as_include, progress_format);
64+
subcommand::config(&subcommand, &parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
6565
}
6666
},
6767
SubCommand::Resource { subcommand } => {

dsc/src/subcommand.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::args::{ConfigSubCommand, DscType, OutputFormat, ResourceSubCommand};
55
use crate::resolve::{get_contents, Include};
66
use crate::resource_command::{get_resource, self};
77
use crate::tablewriter::Table;
8-
use crate::util::{DSC_CONFIG_ROOT, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, get_schema, write_object, get_input, set_dscconfigroot, validate_json};
8+
use crate::util::{get_input, get_schema, in_desired_state, set_dscconfigroot, validate_json, write_object, DSC_CONFIG_ROOT, EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR};
99
use dsc_lib::{
1010
configure::{
1111
config_doc::{
@@ -106,7 +106,7 @@ pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat>
106106
}
107107
}
108108

109-
pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool, as_get: &bool, as_config: &bool)
109+
pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool, as_get: &bool, as_config: &bool, as_assert: &bool)
110110
{
111111
match configurator.invoke_test() {
112112
Ok(result) => {
@@ -115,6 +115,10 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat
115115
let mut result_configuration = Configuration::new();
116116
result_configuration.resources = Vec::new();
117117
for test_result in result.results {
118+
if *as_assert && !in_desired_state(&test_result) {
119+
error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type));
120+
exit(EXIT_DSC_ASSERTION_FAILED);
121+
}
118122
let properties = match test_result.result {
119123
TestResult::Resource(test_response) => {
120124
if test_response.actual_state.is_object() {
@@ -150,6 +154,10 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat
150154
else if *as_get {
151155
let mut group_result = Vec::<ResourceGetResult>::new();
152156
for test_result in result.results {
157+
if *as_assert && !in_desired_state(&test_result) {
158+
error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type));
159+
exit(EXIT_DSC_ASSERTION_FAILED);
160+
}
153161
group_result.push(test_result.into());
154162
}
155163
match serde_json::to_string(&group_result) {
@@ -161,6 +169,14 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat
161169
}
162170
}
163171
else {
172+
if *as_assert {
173+
for test_result in &result.results {
174+
if !in_desired_state(test_result) {
175+
error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type));
176+
exit(EXIT_DSC_ASSERTION_FAILED);
177+
}
178+
}
179+
}
164180
match serde_json::to_string(&(result.results)) {
165181
Ok(json) => json,
166182
Err(err) => {
@@ -252,7 +268,7 @@ fn initialize_config_root(path: Option<&String>) -> Option<String> {
252268
}
253269

254270
#[allow(clippy::too_many_lines)]
255-
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounted_path: Option<&String>, as_group: &bool, as_include: &bool, progress_format: ProgressFormat) {
271+
pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounted_path: Option<&String>, as_group: &bool, as_assert: &bool, as_include: &bool, progress_format: ProgressFormat) {
256272
let (new_parameters, json_string) = match subcommand {
257273
ConfigSubCommand::Get { input, file, .. } |
258274
ConfigSubCommand::Set { input, file, .. } |
@@ -363,7 +379,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounte
363379
config_set(&mut configurator, output_format.as_ref(), as_group);
364380
},
365381
ConfigSubCommand::Test { output_format, as_get, as_config, .. } => {
366-
config_test(&mut configurator, output_format.as_ref(), as_group, as_get, as_config);
382+
config_test(&mut configurator, output_format.as_ref(), as_group, as_get, as_config, as_assert);
367383
},
368384
ConfigSubCommand::Validate { input, file, output_format} => {
369385
let mut result = ValidateResult {

dsc/src/util.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::args::{DscType, OutputFormat, TraceFormat};
55
use crate::resolve::Include;
6+
use dsc_lib::configure::config_result::ResourceTestResult;
67
use dsc_lib::{
78
configure::{
89
config_doc::Configuration,
@@ -54,6 +55,7 @@ pub const EXIT_INVALID_INPUT: i32 = 4;
5455
pub const EXIT_VALIDATION_FAILED: i32 = 5;
5556
pub const EXIT_CTRL_C: i32 = 6;
5657
pub const EXIT_DSC_RESOURCE_NOT_FOUND: i32 = 7;
58+
pub const EXIT_DSC_ASSERTION_FAILED: i32 = 8;
5759

5860
pub const DSC_CONFIG_ROOT: &str = "DSC_CONFIG_ROOT";
5961
pub const DSC_TRACE_LEVEL: &str = "DSC_TRACE_LEVEL";
@@ -530,3 +532,30 @@ pub fn set_dscconfigroot(config_path: &str) -> String
530532

531533
full_path.to_string_lossy().into_owned()
532534
}
535+
536+
537+
/// Check if the test result is in the desired state.
538+
///
539+
/// # Arguments
540+
///
541+
/// * `test_result` - The test result to check
542+
///
543+
/// # Returns
544+
///
545+
/// * `bool` - True if the test result is in the desired state, false otherwise
546+
#[must_use]
547+
pub fn in_desired_state(test_result: &ResourceTestResult) -> bool {
548+
match &test_result.result {
549+
TestResult::Resource(result) => {
550+
result.in_desired_state
551+
},
552+
TestResult::Group(results) => {
553+
for result in results {
554+
if !in_desired_state(result) {
555+
return false;
556+
}
557+
}
558+
true
559+
}
560+
}
561+
}

dsc/tests/dsc_assertion.tests.ps1

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
Describe 'Assertion resource tests' -Skip:(!$IsWindows) {
5+
It 'Example works for <operation>' -TestCases @(
6+
@{ operation = 'get' }
7+
@{ operation = 'set' }
8+
@{ operation = 'test' }
9+
# TODO: Add export to test when https://github.com/PowerShell/DSC/issues/428 is fixed
10+
# @{ operation = 'export' }
11+
) {
12+
param($operation)
13+
$jsonPath = Join-Path $PSScriptRoot '../examples/assertion.dsc.yaml'
14+
$out = dsc config $operation -f $jsonPath 2> "$TestDrive/trace.log"
15+
$LASTEXITCODE | Should -Be 2
16+
$out | Should -BeNullOrEmpty
17+
$log = Get-Content "$TestDrive/trace.log" -Raw
18+
$log | Should -Match '.*Assertion failed.*'
19+
}
20+
}

dsc/tests/dsc_config_test.tests.ps1

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ Describe 'dsc config test tests' {
2121
family: Windows
2222
'@
2323

24-
$out = dsc config test -i $configYaml | ConvertFrom-Json
25-
$LASTEXITCODE | Should -Be 0
26-
24+
$out = dsc config test -i $configYaml 2> "$TestDrive/trace.log" | ConvertFrom-Json
2725
if ($IsWindows) {
26+
$LASTEXITCODE | Should -Be 0
2827
$out.results[0].result.inDesiredState | Should -BeTrue
29-
}
30-
else {
31-
$out.results[0].result.inDesiredState | Should -BeFalse
32-
$out.results[0].result.differingProperties | Should -Contain 'resources'
28+
} else {
29+
$LASTEXITCODE | Should -Be 2
30+
$log = Get-Content "$TestDrive/trace.log" -Raw
31+
$log | Should -Match '.*Assertion failed.*'
3332
}
3433
}
3534

0 commit comments

Comments
 (0)