From 3d442e31405159af76a032afa71acfa9ec05dcda Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 7 Nov 2024 16:49:27 -0800 Subject: [PATCH 01/10] fix output of Assertion resource for test to resemble config and add support for array comparison --- dsc/assertion.dsc.resource.json | 2 +- dsc/src/args.rs | 2 +- dsc/src/subcommand.rs | 41 +++++++--- dsc/tests/dsc_config_test.tests.ps1 | 35 +++++++++ dsc_lib/src/dscresources/dscresource.rs | 100 +++++++++++++++++++++++- 5 files changed, 167 insertions(+), 13 deletions(-) create mode 100644 dsc/tests/dsc_config_test.tests.ps1 diff --git a/dsc/assertion.dsc.resource.json b/dsc/assertion.dsc.resource.json index 8a4fc443..9e678286 100644 --- a/dsc/assertion.dsc.resource.json +++ b/dsc/assertion.dsc.resource.json @@ -37,7 +37,7 @@ "config", "--as-group", "test", - "--as-get" + "--as-test" ], "input": "stdin", "return": "state" diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 07530eec..1f33fd82 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -103,7 +103,7 @@ pub enum ConfigSubCommand { #[clap(short = 'f', long, help = "The output format to use")] format: Option, #[clap(long, hide = true)] - as_get: bool, + as_test: bool, }, #[clap(name = "validate", about = "Validate the current configuration", hide = true)] Validate { diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index dacdd457..e72051eb 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -6,9 +6,9 @@ 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_ARGS, 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::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}}; 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, @@ -93,17 +93,40 @@ pub fn config_set(configurator: &mut Configurator, format: &Option } } -pub fn config_test(configurator: &mut Configurator, format: &Option, as_group: &bool, as_get: &bool) +pub fn config_test(configurator: &mut Configurator, format: &Option, 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::::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}"); @@ -294,8 +317,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte 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 { diff --git a/dsc/tests/dsc_config_test.tests.ps1 b/dsc/tests/dsc_config_test.tests.ps1 new file mode 100644 index 00000000..c6ad1be4 --- /dev/null +++ b/dsc/tests/dsc_config_test.tests.ps1 @@ -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' + } + } +} diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index b72ab43d..87f5001f 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -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}; @@ -363,22 +363,42 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec { 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()); }, } @@ -388,3 +408,79 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec { diff_properties } + +/// Compares two arrays independent of order +fn is_same_array(expected: &Vec, actual: &Vec) -> 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, 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 +} From 0219972dd4fb45de77beb554ae91b4b11c851744 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 7 Nov 2024 17:08:03 -0800 Subject: [PATCH 02/10] fix clippy --- dsc_lib/src/dscresources/dscresource.rs | 92 +++++++++++-------------- 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index 87f5001f..4b876b37 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -338,6 +338,16 @@ pub fn get_well_known_properties() -> HashMap { } #[must_use] +/// Performs a comparison of two JSON Values if the expected is a strict subset of the actual +/// +/// # Arguments +/// +/// * `expected` - The expected value +/// * `actual` - The actual value +/// +/// # Returns +/// +/// An array of top level properties that differ, if any pub fn get_diff(expected: &Value, actual: &Value) -> Vec { let mut diff_properties: Vec = Vec::new(); if expected.is_null() { @@ -373,34 +383,28 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec { continue; } - match actual.as_object() { - Some(actual_object) => { - if actual_object.contains_key(key) { - if value.is_array() { - if !actual[key].is_array() { - info!("diff: {} is not an array", actual[key]); + if let Some(actual_object) = actual.as_object() { + if actual_object.contains_key(key) { + if let Some(value_array) = value.as_array() { + if let Some(actual_array) = actual[key].as_array() { + if !is_same_array(value_array, actual_array) { + info!("diff: arrays differ for {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] { + } else { + info!("diff: {} is not an array", actual[key]); diff_properties.push(key.to_string()); } - } - else { - info!("diff: {key} missing"); + } else if value != &actual[key] { diff_properties.push(key.to_string()); } - }, - None => { - info!("diff: {key} not object"); + } else { + info!("diff: {key} missing"); diff_properties.push(key.to_string()); - }, + } + } else { + info!("diff: {key} not object"); + diff_properties.push(key.to_string()); } } } @@ -417,7 +421,7 @@ fn is_same_array(expected: &Vec, actual: &Vec) -> bool { } for item in expected { - if !array_contains(&actual, &item) { + if !array_contains(actual, item) { info!("diff: actual array missing expected element"); return false; } @@ -428,57 +432,43 @@ fn is_same_array(expected: &Vec, actual: &Vec) -> bool { fn array_contains(array: &Vec, 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_boolean() && item.is_boolean() && 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_f64() && item.is_f64() && (find.as_f64().unwrap() - item.as_f64().unwrap()).abs() < 0.1 { + return true; } - if find.is_i64() && item.is_i64() { - if find.as_i64().unwrap() == item.as_i64().unwrap() { - return true; - } + if find.is_i64() && item.is_i64() && 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_number() && item.is_number() && 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_string() && item.is_string() && 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_u64() && item.is_u64() && 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 { + if obj_diff.is_empty() { return true; } } - if find.is_array() && item.is_array() { - if array_contains(item.as_array().unwrap(), find) { - return true; - } + if find.is_array() && item.is_array() && array_contains(item.as_array().unwrap(), find) { + return true; } } From 1614fce28130909e777ce98c4a30c35cd60b9d1b Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 7 Nov 2024 17:21:00 -0800 Subject: [PATCH 03/10] add test tracing --- resources/apt/test/apt.tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/apt/test/apt.tests.ps1 b/resources/apt/test/apt.tests.ps1 index 17338865..1962e644 100644 --- a/resources/apt/test/apt.tests.ps1 +++ b/resources/apt/test/apt.tests.ps1 @@ -27,8 +27,8 @@ Describe 'Apt resource tests' { if (-not $aptExists) { Set-ItResult -Skip -Because "Apt not found" } - $out = dsc config get -p $yamlPath | ConvertFrom-Json -Depth 10 - $LASTEXITCODE | Should -Be 0 + $out = dsc config get -p $yamlPath 2> "$TestDrive/stderr.txt" | ConvertFrom-Json -Depth 10 + $LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TestDrive/stderr.txt") $exists = $null -ne (Get-Command $pkgName -CommandType Application -ErrorAction Ignore) $observed = $out.results[1].result.actualState._exist $observed | Should -Be $exists From 42c8b260fac4a5575ff686e6bdc7f1db59b3fb91 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 7 Nov 2024 17:33:18 -0800 Subject: [PATCH 04/10] set tracing level for test --- resources/apt/test/apt.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/apt/test/apt.tests.ps1 b/resources/apt/test/apt.tests.ps1 index 1962e644..0f88b4b8 100644 --- a/resources/apt/test/apt.tests.ps1 +++ b/resources/apt/test/apt.tests.ps1 @@ -27,7 +27,7 @@ Describe 'Apt resource tests' { if (-not $aptExists) { Set-ItResult -Skip -Because "Apt not found" } - $out = dsc config get -p $yamlPath 2> "$TestDrive/stderr.txt" | ConvertFrom-Json -Depth 10 + $out = dsc -l trace config get -p $yamlPath 2> "$TestDrive/stderr.txt" | ConvertFrom-Json -Depth 10 $LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TestDrive/stderr.txt") $exists = $null -ne (Get-Command $pkgName -CommandType Application -ErrorAction Ignore) $observed = $out.results[1].result.actualState._exist From f879b86a62004eed6aaea991621ed1db095306ea Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 7 Nov 2024 19:07:28 -0800 Subject: [PATCH 05/10] change writing of test trace --- resources/apt/test/apt.tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/apt/test/apt.tests.ps1 b/resources/apt/test/apt.tests.ps1 index 0f88b4b8..743232c1 100644 --- a/resources/apt/test/apt.tests.ps1 +++ b/resources/apt/test/apt.tests.ps1 @@ -28,7 +28,8 @@ Describe 'Apt resource tests' { Set-ItResult -Skip -Because "Apt not found" } $out = dsc -l trace config get -p $yamlPath 2> "$TestDrive/stderr.txt" | ConvertFrom-Json -Depth 10 - $LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TestDrive/stderr.txt") + write-verbose -verbose (Get-Content "$TestDrive/stderr.txt" | Out-String) + $LASTEXITCODE | Should -Be 0 $exists = $null -ne (Get-Command $pkgName -CommandType Application -ErrorAction Ignore) $observed = $out.results[1].result.actualState._exist $observed | Should -Be $exists From ee53f45ac57f6effe33fd19f83cd1a67329ca716 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 7 Nov 2024 19:25:37 -0800 Subject: [PATCH 06/10] add back as_get --- dsc/src/args.rs | 2 ++ dsc/src/subcommand.rs | 38 ++++++++++++++++++++++++++------ resources/apt/test/apt.tests.ps1 | 3 +-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 1f33fd82..e3627fb8 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -103,6 +103,8 @@ pub enum ConfigSubCommand { #[clap(short = 'f', long, help = "The output format to use")] format: Option, #[clap(long, hide = true)] + as_get: bool, + #[clap(long, hide = true)] as_test: bool, }, #[clap(name = "validate", about = "Validate the current configuration", hide = true)] diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index e72051eb..0891ad1f 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -6,12 +6,23 @@ 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_ARGS, 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, Resource}}; -use dsc_lib::dscerror::DscError; -use dsc_lib::dscresources::invoke_result::{ResolveResult, TestResult}; use dsc_lib::{ + configure::{ + config_doc::{ + Configuration, + ExecutionKind, + Resource, + }, + config_result::ResourceGetResult, + Configurator, + }, + dscerror::DscError, DscManager, - dscresources::invoke_result::ValidateResult, + dscresources::invoke_result::{ + ResolveResult, + TestResult, + ValidateResult, + }, dscresources::dscresource::{Capability, ImplementedAs, Invoke}, dscresources::resource_manifest::{import_manifest, ResourceManifest}, }; @@ -93,7 +104,7 @@ pub fn config_set(configurator: &mut Configurator, format: &Option } } -pub fn config_test(configurator: &mut Configurator, format: &Option, as_group: &bool, as_test: &bool) +pub fn config_test(configurator: &mut Configurator, format: &Option, as_group: &bool, as_get: &bool, as_test: &bool) { match configurator.invoke_test() { Ok(result) => { @@ -134,6 +145,19 @@ pub fn config_test(configurator: &mut Configurator, format: &Option::new(); + for test_result in result.results { + group_result.push(test_result.into()); + } + match serde_json::to_string(&group_result) { + Ok(json) => json, + Err(err) => { + error!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); + } + } + } else { match serde_json::to_string(&(result.results)) { Ok(json) => json, @@ -317,8 +341,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte ConfigSubCommand::Set { format, .. } => { config_set(&mut configurator, format, as_group); }, - ConfigSubCommand::Test { format, as_test, .. } => { - config_test(&mut configurator, format, as_group, as_test); + ConfigSubCommand::Test { format, as_get, as_test, .. } => { + config_test(&mut configurator, format, as_group, as_get, as_test); }, ConfigSubCommand::Validate { document, path, format} => { let mut result = ValidateResult { diff --git a/resources/apt/test/apt.tests.ps1 b/resources/apt/test/apt.tests.ps1 index 743232c1..af00e4ce 100644 --- a/resources/apt/test/apt.tests.ps1 +++ b/resources/apt/test/apt.tests.ps1 @@ -28,8 +28,7 @@ Describe 'Apt resource tests' { Set-ItResult -Skip -Because "Apt not found" } $out = dsc -l trace config get -p $yamlPath 2> "$TestDrive/stderr.txt" | ConvertFrom-Json -Depth 10 - write-verbose -verbose (Get-Content "$TestDrive/stderr.txt" | Out-String) - $LASTEXITCODE | Should -Be 0 + $LASTEXITCODE | Should -Be 0 -Because (Get-Content "$TestDrive/stderr.txt" | Out-String) $exists = $null -ne (Get-Command $pkgName -CommandType Application -ErrorAction Ignore) $observed = $out.results[1].result.actualState._exist $observed | Should -Be $exists From 9cb2b4490e5f8f4ec1fd092e299dbdd31ca846ab Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 8 Nov 2024 10:56:36 -0800 Subject: [PATCH 07/10] rename `--as-test` to `--as-config` as it's more accurate, added unit tests, fixed nested array search --- dsc/src/args.rs | 2 +- dsc/src/subcommand.rs | 8 +-- dsc_lib/src/dscresources/dscresource.rs | 66 ++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/dsc/src/args.rs b/dsc/src/args.rs index e3627fb8..a096ad98 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -105,7 +105,7 @@ pub enum ConfigSubCommand { #[clap(long, hide = true)] as_get: bool, #[clap(long, hide = true)] - as_test: bool, + as_config: bool, }, #[clap(name = "validate", about = "Validate the current configuration", hide = true)] Validate { diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 0891ad1f..9db47b2b 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -104,12 +104,12 @@ pub fn config_set(configurator: &mut Configurator, format: &Option } } -pub fn config_test(configurator: &mut Configurator, format: &Option, as_group: &bool, as_get: &bool, as_test: &bool) +pub fn config_test(configurator: &mut Configurator, format: &Option, as_group: &bool, as_get: &bool, as_config: &bool) { match configurator.invoke_test() { Ok(result) => { if *as_group { - let json = if *as_test { + let json = if *as_config { let mut result_configuration = Configuration::new(); result_configuration.resources = Vec::new(); for test_result in result.results { @@ -341,8 +341,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte ConfigSubCommand::Set { format, .. } => { config_set(&mut configurator, format, as_group); }, - ConfigSubCommand::Test { format, as_get, as_test, .. } => { - config_test(&mut configurator, format, as_group, as_get, as_test); + ConfigSubCommand::Test { format, as_get, as_config, .. } => { + config_test(&mut configurator, format, as_group, as_get, as_config); }, ConfigSubCommand::Validate { document, path, format} => { let mut result = ValidateResult { diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index 4b876b37..f82f9bf3 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -467,10 +467,74 @@ fn array_contains(array: &Vec, find: &Value) -> bool { } } - if find.is_array() && item.is_array() && array_contains(item.as_array().unwrap(), find) { + if find.is_array() && item.is_array() && is_same_array(item.as_array().unwrap(), find.as_array().unwrap()) { return true; } } false } + +#[test] +fn same_array() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(null)]; + let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(null)]; + assert_eq!(is_same_array(&array_one, &array_two), true); +} + +#[test] +fn same_array_out_of_order() { + use serde_json::json; + let array_one = vec![json!("a"), json!(true), json!(r#"{"a":"b"}"#)]; + let array_two = vec![json!(r#"{"a":"b"}"#), json!("a"), json!(true)]; + assert_eq!(is_same_array(&array_one, &array_two), true); +} + +#[test] +fn different_array() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#)]; + let array_two = vec![json!(r#"{"a":"b"}"#), json!("a"), json!(2)]; + assert_eq!(is_same_array(&array_one, &array_two), false); +} + +#[test] +fn different_array_sizes() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#)]; + let array_two = vec![json!(r#"{"a":"b"}"#), json!("a")]; + assert_eq!(is_same_array(&array_one, &array_two), false); +} + +#[test] +fn array_with_multiple_objects_with_superset() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(r#"{"c":"d"}"#)]; + let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b", "c":"d"}"#), json!(r#"{"c":"d"}"#)]; + assert_eq!(is_same_array(&array_one, &array_two), false); +} + +#[test] +fn array_with_duplicates_out_of_order() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(r#"{"a":"b"}"#)]; + let array_two = vec![json!(r#"{"a":"b"}"#), json!("a"), json!(1), json!(r#"{"a":"b"}"#)]; + assert_eq!(is_same_array(&array_one, &array_two), true); +} + +#[test] +fn same_array_with_nested_array() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(1)])]; + let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(1)])]; + assert_eq!(is_same_array(&array_one, &array_two), true); +} + +#[test] +fn different_array_with_nested_array() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(1)])]; + let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(2)])]; + assert_eq!(is_same_array(&array_one, &array_two), false); +} From b86a998047851a740b81b4f1b125cbfdb3009c39 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 8 Nov 2024 11:06:13 -0800 Subject: [PATCH 08/10] fix resource manifest --- dsc/assertion.dsc.resource.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/assertion.dsc.resource.json b/dsc/assertion.dsc.resource.json index 9e678286..17142f8a 100644 --- a/dsc/assertion.dsc.resource.json +++ b/dsc/assertion.dsc.resource.json @@ -37,7 +37,7 @@ "config", "--as-group", "test", - "--as-test" + "--as-config" ], "input": "stdin", "return": "state" From 7e1bf929002a3b92106833cc5410b36f86a92149 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 13 Nov 2024 14:16:40 -0800 Subject: [PATCH 09/10] address initial feedback --- dsc/src/args.rs | 2 ++ dsc_lib/src/dscresources/dscresource.rs | 42 +++++++++++++++---------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/dsc/src/args.rs b/dsc/src/args.rs index a096ad98..8a29f146 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -102,8 +102,10 @@ pub enum ConfigSubCommand { path: Option, #[clap(short = 'f', long, help = "The output format to use")] format: Option, + // Used by Assertion resource to return `test` result as a `get` result #[clap(long, hide = true)] as_get: bool, + // Used by Assertion resource to return `test` result as a configuration `test` result #[clap(long, hide = true)] as_config: bool, }, diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index f82f9bf3..7c62bda1 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -478,63 +478,71 @@ fn array_contains(array: &Vec, find: &Value) -> bool { #[test] fn same_array() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(null)]; - let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(null)]; + let array_one = vec![json!("a"), json!(1), json!({"a":"b"}), json!(null)]; + let array_two = vec![json!("a"), json!(1), json!({"a":"b"}), json!(null)]; assert_eq!(is_same_array(&array_one, &array_two), true); } #[test] fn same_array_out_of_order() { use serde_json::json; - let array_one = vec![json!("a"), json!(true), json!(r#"{"a":"b"}"#)]; - let array_two = vec![json!(r#"{"a":"b"}"#), json!("a"), json!(true)]; + let array_one = vec![json!("a"), json!(true), json!({"a":"b"})]; + let array_two = vec![json!({"a":"b"}), json!("a"), json!(true)]; assert_eq!(is_same_array(&array_one, &array_two), true); } #[test] fn different_array() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#)]; - let array_two = vec![json!(r#"{"a":"b"}"#), json!("a"), json!(2)]; + let array_one = vec![json!("a"), json!(1), json!({"a":"b"})]; + let array_two = vec![json!({"a":"b"}), json!("a"), json!(2)]; assert_eq!(is_same_array(&array_one, &array_two), false); } #[test] fn different_array_sizes() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#)]; - let array_two = vec![json!(r#"{"a":"b"}"#), json!("a")]; + let array_one = vec![json!("a"), json!(1), json!({"a":"b"})]; + let array_two = vec![json!({"a":"b"}), json!("a")]; assert_eq!(is_same_array(&array_one, &array_two), false); } #[test] -fn array_with_multiple_objects_with_superset() { +fn array_with_multiple_objects_with_actual_superset() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(r#"{"c":"d"}"#)]; - let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b", "c":"d"}"#), json!(r#"{"c":"d"}"#)]; + let array_one = vec![json!("a"), json!(1), json!({"a":"b"}), json!({"c":"d"})]; + let array_two = vec![json!("a"), json!(1), json!({"c":"d", "a":"b"}), json!({"c":"d"})]; + assert_eq!(is_same_array(&array_one, &array_two), true); +} + +#[test] +fn array_with_multiple_objects_with_expected_superset() { + use serde_json::json; + let array_one = vec![json!("a"), json!(1), json!({"a":"b", "c":"d"}), json!({"c":"d"})]; + let array_two = vec![json!("a"), json!(1), json!({"a":"b"}), json!({"c":"d"})]; assert_eq!(is_same_array(&array_one, &array_two), false); } #[test] fn array_with_duplicates_out_of_order() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(r#"{"a":"b"}"#)]; - let array_two = vec![json!(r#"{"a":"b"}"#), json!("a"), json!(1), json!(r#"{"a":"b"}"#)]; + let array_one = vec![json!("a"), json!(1), json!({"a":"b"}), json!({"a":"b"})]; + let array_two = vec![json!({"a":"b"}), json!("a"), json!(1), json!({"a":"b"})]; assert_eq!(is_same_array(&array_one, &array_two), true); } #[test] fn same_array_with_nested_array() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(1)])]; - let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(1)])]; + let array_one = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(1)])]; + let array_two = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(1)])]; assert_eq!(is_same_array(&array_one, &array_two), true); } #[test] fn different_array_with_nested_array() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(1)])]; - let array_two = vec![json!("a"), json!(1), json!(r#"{"a":"b"}"#), json!(vec![json!("a"), json!(2)])]; + let array_one = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(1)])]; + let array_two = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(2)])]; assert_eq!(is_same_array(&array_one, &array_two), false); } From 4036a5d7e9a181a4eb371dc63e57860a6f4a06ac Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Sat, 16 Nov 2024 17:29:39 -0800 Subject: [PATCH 10/10] fix clippy --- dsc_lib/src/discovery/command_discovery.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 6d0e26a5..4480fdb4 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -95,7 +95,7 @@ impl CommandDiscovery { Ok(v) => { resource_path_setting = v; }, - Err(e) => { + Err(e) => { debug!("{e}"); } } @@ -144,7 +144,7 @@ impl CommandDiscovery { paths.push(exe_home_pb); if let Ok(new_path) = env::join_paths(paths.clone()) { - env::set_var("PATH", &new_path); + env::set_var("PATH", new_path); } } } @@ -369,7 +369,7 @@ impl ResourceDiscovery for CommandDiscovery { } else { self.discover_resources("*")?; self.discover_adapted_resources(type_name_filter, adapter_name_filter)?; - + // add/update found adapted resources to the lookup_table add_resources_to_lookup_table(&self.adapted_resources); @@ -652,7 +652,7 @@ fn save_adapted_resources_lookup_table(lookup_table: &HashMap) fn load_adapted_resources_lookup_table() -> HashMap { let file_path = get_lookup_table_file_path(); - + let lookup_table: HashMap = match fs::read(file_path.clone()){ Ok(data) => { serde_json::from_slice(&data).unwrap_or_default() }, Err(_) => { HashMap::new() }