Skip to content

Commit 3d442e3

Browse files
committed
fix output of Assertion resource for test to resemble config and add support for array comparison
1 parent 51ebbe3 commit 3d442e3

File tree

5 files changed

+167
-13
lines changed

5 files changed

+167
-13
lines changed

dsc/assertion.dsc.resource.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"config",
3838
"--as-group",
3939
"test",
40-
"--as-get"
40+
"--as-test"
4141
],
4242
"input": "stdin",
4343
"return": "state"

dsc/src/args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub enum ConfigSubCommand {
103103
#[clap(short = 'f', long, help = "The output format to use")]
104104
format: Option<OutputFormat>,
105105
#[clap(long, hide = true)]
106-
as_get: bool,
106+
as_test: bool,
107107
},
108108
#[clap(name = "validate", about = "Validate the current configuration", hide = true)]
109109
Validate {

dsc/src/subcommand.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::resolve::{get_contents, Include};
66
use crate::resource_command::{get_resource, self};
77
use crate::tablewriter::Table;
88
use crate::util::{DSC_CONFIG_ROOT, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, get_schema, write_output, get_input, set_dscconfigroot, validate_json};
9-
use dsc_lib::configure::{Configurator, config_doc::{Configuration, ExecutionKind}, config_result::ResourceGetResult};
9+
use dsc_lib::configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}};
1010
use dsc_lib::dscerror::DscError;
11-
use dsc_lib::dscresources::invoke_result::ResolveResult;
11+
use dsc_lib::dscresources::invoke_result::{ResolveResult, TestResult};
1212
use dsc_lib::{
1313
DscManager,
1414
dscresources::invoke_result::ValidateResult,
@@ -93,17 +93,40 @@ pub fn config_set(configurator: &mut Configurator, format: &Option<OutputFormat>
9393
}
9494
}
9595

96-
pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat>, as_group: &bool, as_get: &bool)
96+
pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat>, as_group: &bool, as_test: &bool)
9797
{
9898
match configurator.invoke_test() {
9999
Ok(result) => {
100100
if *as_group {
101-
let json = if *as_get {
102-
let mut group_result = Vec::<ResourceGetResult>::new();
101+
let json = if *as_test {
102+
let mut result_configuration = Configuration::new();
103+
result_configuration.resources = Vec::new();
103104
for test_result in result.results {
104-
group_result.push(test_result.into());
105+
let properties = match test_result.result {
106+
TestResult::Resource(test_response) => {
107+
if test_response.actual_state.is_object() {
108+
test_response.actual_state.as_object().cloned()
109+
} else {
110+
debug!("actual_state is not an object");
111+
None
112+
}
113+
},
114+
TestResult::Group(_) => {
115+
// not expected
116+
debug!("Unexpected Group TestResult");
117+
None
118+
}
119+
};
120+
let resource = Resource {
121+
name: test_result.name,
122+
resource_type: test_result.resource_type,
123+
properties,
124+
depends_on: None,
125+
metadata: None,
126+
};
127+
result_configuration.resources.push(resource);
105128
}
106-
match serde_json::to_string(&group_result) {
129+
match serde_json::to_string(&result_configuration) {
107130
Ok(json) => json,
108131
Err(err) => {
109132
error!("JSON Error: {err}");
@@ -294,8 +317,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, mounte
294317
ConfigSubCommand::Set { format, .. } => {
295318
config_set(&mut configurator, format, as_group);
296319
},
297-
ConfigSubCommand::Test { format, as_get, .. } => {
298-
config_test(&mut configurator, format, as_group, as_get);
320+
ConfigSubCommand::Test { format, as_test, .. } => {
321+
config_test(&mut configurator, format, as_group, as_test);
299322
},
300323
ConfigSubCommand::Validate { document, path, format} => {
301324
let mut result = ValidateResult {

dsc/tests/dsc_config_test.tests.ps1

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
Describe 'dsc config test tests' {
5+
It 'Assertion works correctly' {
6+
$configYaml = @'
7+
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json
8+
resources:
9+
- name: Operating System Assertion
10+
type: Microsoft.DSC/Assertion
11+
properties:
12+
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json
13+
resources:
14+
- name: Is64BitOS
15+
type: Microsoft/OSInfo
16+
properties:
17+
bitness: '64'
18+
- name: 64bit test 2
19+
type: Microsoft/OSInfo
20+
properties:
21+
family: Windows
22+
'@
23+
24+
$out = dsc config test -d $configYaml | ConvertFrom-Json
25+
$LASTEXITCODE | Should -Be 0
26+
27+
if ($IsWindows) {
28+
$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'
33+
}
34+
}
35+
}

dsc_lib/src/dscresources/dscresource.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
77
use serde::{Deserialize, Serialize};
88
use serde_json::Value;
99
use std::collections::HashMap;
10-
use tracing::debug;
10+
use tracing::{debug, info};
1111

1212
use super::{command_resource, dscerror, invoke_result::{ExportResult, GetResult, ResolveResult, ResourceTestResponse, SetResult, TestResult, ValidateResult}, resource_manifest::import_manifest};
1313

@@ -363,22 +363,42 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
363363
if value.is_object() {
364364
let sub_diff = get_diff(value, &actual[key]);
365365
if !sub_diff.is_empty() {
366+
debug!("diff: sub diff for {key}");
366367
diff_properties.push(key.to_string());
367368
}
368369
}
369370
else {
371+
// skip `$schema` key as that is provided as input, but not output typically
372+
if key == "$schema" {
373+
continue;
374+
}
375+
370376
match actual.as_object() {
371377
Some(actual_object) => {
372378
if actual_object.contains_key(key) {
373-
if value != &actual[key] {
379+
if value.is_array() {
380+
if !actual[key].is_array() {
381+
info!("diff: {} is not an array", actual[key]);
382+
diff_properties.push(key.to_string());
383+
}
384+
else {
385+
if !is_same_array(&value.as_array().unwrap(), &actual[key].as_array().unwrap()) {
386+
info!("diff: arrays differ for {key}");
387+
diff_properties.push(key.to_string());
388+
}
389+
}
390+
}
391+
else if value != &actual[key] {
374392
diff_properties.push(key.to_string());
375393
}
376394
}
377395
else {
396+
info!("diff: {key} missing");
378397
diff_properties.push(key.to_string());
379398
}
380399
},
381400
None => {
401+
info!("diff: {key} not object");
382402
diff_properties.push(key.to_string());
383403
},
384404
}
@@ -388,3 +408,79 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
388408

389409
diff_properties
390410
}
411+
412+
/// Compares two arrays independent of order
413+
fn is_same_array(expected: &Vec<Value>, actual: &Vec<Value>) -> bool {
414+
if expected.len() != actual.len() {
415+
info!("diff: arrays are different lengths");
416+
return false;
417+
}
418+
419+
for item in expected {
420+
if !array_contains(&actual, &item) {
421+
info!("diff: actual array missing expected element");
422+
return false;
423+
}
424+
}
425+
426+
true
427+
}
428+
429+
fn array_contains(array: &Vec<Value>, find: &Value) -> bool {
430+
for item in array {
431+
if find.is_boolean() && item.is_boolean() {
432+
if find.as_bool().unwrap() == item.as_bool().unwrap() {
433+
return true;
434+
}
435+
}
436+
437+
if find.is_f64() && item.is_f64() {
438+
if find.as_f64().unwrap() == item.as_f64().unwrap() {
439+
return true;
440+
}
441+
}
442+
443+
if find.is_i64() && item.is_i64() {
444+
if find.as_i64().unwrap() == item.as_i64().unwrap() {
445+
return true;
446+
}
447+
}
448+
449+
if find.is_null() && item.is_null() {
450+
return true;
451+
}
452+
453+
if find.is_number() && item.is_number() {
454+
if find.as_number().unwrap() == item.as_number().unwrap() {
455+
return true;
456+
}
457+
}
458+
459+
if find.is_string() && item.is_string() {
460+
if find.as_str().unwrap() == item.as_str().unwrap() {
461+
return true;
462+
}
463+
}
464+
465+
if find.is_u64() && item.is_u64() {
466+
if find.as_u64().unwrap() == item.as_u64().unwrap() {
467+
return true;
468+
}
469+
}
470+
471+
if find.is_object() && item.is_object() {
472+
let obj_diff = get_diff(find, item);
473+
if obj_diff.len() == 0 {
474+
return true;
475+
}
476+
}
477+
478+
if find.is_array() && item.is_array() {
479+
if array_contains(item.as_array().unwrap(), find) {
480+
return true;
481+
}
482+
}
483+
}
484+
485+
false
486+
}

0 commit comments

Comments
 (0)