Skip to content

Commit

Permalink
fix output of Assertion resource for test to resemble config and add …
Browse files Browse the repository at this point in the history
…support for array comparison
  • Loading branch information
SteveL-MSFT committed Nov 8, 2024
1 parent c209290 commit 219cb97
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 13 deletions.
2 changes: 1 addition & 1 deletion dsc/assertion.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"config",
"--as-group",
"test",
"--as-get"
"--as-test"
],
"input": "stdin",
"return": "state"
Expand Down
2 changes: 1 addition & 1 deletion dsc/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub enum ConfigSubCommand {
#[clap(short = 'f', long, help = "The output format to use")]
format: Option<OutputFormat>,
#[clap(long, hide = true)]
as_get: bool,
as_test: bool,
},
#[clap(name = "validate", about = "Validate the current configuration", hide = true)]
Validate {
Expand Down
42 changes: 33 additions & 9 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use crate::resolve::{get_contents, Include};
use crate::resource_command::{get_resource, self};
use crate::tablewriter::Table;
use crate::util::{DSC_CONFIG_ROOT, EXIT_DSC_ERROR, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, get_schema, write_output, get_input, set_dscconfigroot, validate_json};
use dsc_lib::configure::{Configurator, config_doc::{Configuration, ExecutionKind}, config_result::ResourceGetResult};
use dsc_lib::configure::config_doc::Resource;
use dsc_lib::configure::{Configurator, config_doc::{Configuration, ExecutionKind}};
use dsc_lib::dscerror::DscError;
use dsc_lib::dscresources::invoke_result::ResolveResult;
use dsc_lib::dscresources::invoke_result::{ResolveResult, TestResult};
use dsc_lib::{
DscManager,
dscresources::invoke_result::ValidateResult,
Expand Down Expand Up @@ -90,17 +91,40 @@ pub fn config_set(configurator: &mut Configurator, format: &Option<OutputFormat>
}
}

pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat>, as_group: &bool, as_get: &bool)
pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat>, as_group: &bool, as_test: &bool)
{
match configurator.invoke_test() {
Ok(result) => {
if *as_group {
let json = if *as_get {
let mut group_result = Vec::<ResourceGetResult>::new();
let json = if *as_test {
let mut result_configuration = Configuration::new();
result_configuration.resources = Vec::new();
for test_result in result.results {
group_result.push(test_result.into());
let properties = match test_result.result {
TestResult::Resource(test_response) => {
if test_response.actual_state.is_object() {
test_response.actual_state.as_object().cloned()
} else {
debug!("actual_state is not an object");
None
}
},
TestResult::Group(_) => {
// not expected
debug!("Unexpected Group TestResult");
None
}
};
let resource = Resource {
name: test_result.name,
resource_type: test_result.resource_type,
properties,
depends_on: None,
metadata: None,
};
result_configuration.resources.push(resource);
}
match serde_json::to_string(&group_result) {
match serde_json::to_string(&result_configuration) {
Ok(json) => json,
Err(err) => {
error!("JSON Error: {err}");
Expand Down Expand Up @@ -282,8 +306,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, stdin:
ConfigSubCommand::Set { format, .. } => {
config_set(&mut configurator, format, as_group);
},
ConfigSubCommand::Test { format, as_get, .. } => {
config_test(&mut configurator, format, as_group, as_get);
ConfigSubCommand::Test { format, as_test, .. } => {
config_test(&mut configurator, format, as_group, as_test);
},
ConfigSubCommand::Validate { document, path, format} => {
let mut result = ValidateResult {
Expand Down
35 changes: 35 additions & 0 deletions dsc/tests/dsc_config_test.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe 'dsc config test tests' {
It 'Assertion works correctly' {
$configYaml = @'
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json
resources:
- name: Operating System Assertion
type: Microsoft.DSC/Assertion
properties:
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json
resources:
- name: Is64BitOS
type: Microsoft/OSInfo
properties:
bitness: '64'
- name: 64bit test 2
type: Microsoft/OSInfo
properties:
family: Windows
'@

$out = dsc config test -d $configYaml | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0

if ($IsWindows) {
$out.results[0].result.inDesiredState | Should -BeTrue
}
else {
$out.results[0].result.inDesiredState | Should -BeFalse
$out.results[0].result.differingProperties | Should -Contain 'resources'
}
}
}
100 changes: 98 additions & 2 deletions dsc_lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use tracing::debug;
use tracing::{debug, info};

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

Expand Down Expand Up @@ -363,22 +363,42 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
if value.is_object() {
let sub_diff = get_diff(value, &actual[key]);
if !sub_diff.is_empty() {
debug!("diff: sub diff for {key}");
diff_properties.push(key.to_string());
}
}
else {
// skip `$schema` key as that is provided as input, but not output typically
if key == "$schema" {
continue;
}

match actual.as_object() {
Some(actual_object) => {
if actual_object.contains_key(key) {
if value != &actual[key] {
if value.is_array() {
if !actual[key].is_array() {
info!("diff: {} is not an array", actual[key]);
diff_properties.push(key.to_string());
}
else {
if !is_same_array(&value.as_array().unwrap(), &actual[key].as_array().unwrap()) {
info!("diff: arrays differ for {key}");
diff_properties.push(key.to_string());
}
}
}
else if value != &actual[key] {
diff_properties.push(key.to_string());
}
}
else {
info!("diff: {key} missing");
diff_properties.push(key.to_string());
}
},
None => {
info!("diff: {key} not object");
diff_properties.push(key.to_string());
},
}
Expand All @@ -388,3 +408,79 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {

diff_properties
}

/// Compares two arrays independent of order
fn is_same_array(expected: &Vec<Value>, actual: &Vec<Value>) -> bool {
if expected.len() != actual.len() {
info!("diff: arrays are different lengths");
return false;
}

for item in expected {
if !array_contains(&actual, &item) {
info!("diff: actual array missing expected element");
return false;
}
}

true
}

fn array_contains(array: &Vec<Value>, find: &Value) -> bool {
for item in array {
if find.is_boolean() && item.is_boolean() {
if find.as_bool().unwrap() == item.as_bool().unwrap() {
return true;
}
}

if find.is_f64() && item.is_f64() {
if find.as_f64().unwrap() == item.as_f64().unwrap() {
return true;
}
}

if find.is_i64() && item.is_i64() {
if find.as_i64().unwrap() == item.as_i64().unwrap() {
return true;
}
}

if find.is_null() && item.is_null() {
return true;
}

if find.is_number() && item.is_number() {
if find.as_number().unwrap() == item.as_number().unwrap() {
return true;
}
}

if find.is_string() && item.is_string() {
if find.as_str().unwrap() == item.as_str().unwrap() {
return true;
}
}

if find.is_u64() && item.is_u64() {
if find.as_u64().unwrap() == item.as_u64().unwrap() {
return true;
}
}

if find.is_object() && item.is_object() {
let obj_diff = get_diff(find, item);
if obj_diff.len() == 0 {
return true;
}
}

if find.is_array() && item.is_array() {
if array_contains(item.as_array().unwrap(), find) {
return true;
}
}
}

false
}

0 comments on commit 219cb97

Please sign in to comment.