From 5674e0ac492368ca82783c4c244763a24d332f4c Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 4 Jun 2026 11:28:09 -0400 Subject: [PATCH 1/6] Migrate utils module Groovy to Java tests --- .../trace/api/ConfigSettingTest.groovy | 85 ----- .../api/env/CapturedEnvironmentTest.groovy | 151 -------- .../provider/AgentArgsInjectorTest.groovy | 18 - .../provider/AgentArgsParserTest.groovy | 79 ----- .../provider/ConfigConverterTest.groovy | 195 ----------- .../PropertiesConfigSourceTest.groovy | 36 -- .../StableConfigMappingExceptionTest.groovy | 35 -- .../provider/StableConfigSourceTest.groovy | 322 ----------------- .../datadog/trace/api/ConfigSettingTest.java | 119 +++++++ .../api/env/CapturedEnvironmentTest.java | 168 +++++++++ .../provider/AgentArgsInjectorTest.java | 26 ++ .../config/provider/AgentArgsParserTest.java | 65 ++++ .../config/provider/ConfigConverterTest.java | 232 +++++++++++++ .../provider/PropertiesConfigSourceTest.java | 41 +++ .../provider/StableConfigSourceTest.java | 327 ++++++++++++++++++ .../StableConfigMappingExceptionTest.java | 44 +++ 16 files changed, 1022 insertions(+), 921 deletions(-) delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/api/ConfigSettingTest.groovy delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/api/env/CapturedEnvironmentTest.groovy delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.groovy delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.groovy delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigConverterTest.groovy delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.groovy delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.groovy delete mode 100644 utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy create mode 100644 utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java create mode 100644 utils/config-utils/src/test/java/datadog/trace/api/env/CapturedEnvironmentTest.java create mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.java create mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.java create mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java create mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.java create mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java create mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java diff --git a/utils/config-utils/src/test/groovy/datadog/trace/api/ConfigSettingTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/api/ConfigSettingTest.groovy deleted file mode 100644 index 71a58e144d2..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/api/ConfigSettingTest.groovy +++ /dev/null @@ -1,85 +0,0 @@ -package datadog.trace.api - -import spock.lang.Specification - -class ConfigSettingTest extends Specification { - - def "supports equality check"() { - when: - def cs1 = ConfigSetting.of(key1, value1, origin1) - def cs2 = ConfigSetting.of(key2, value2, origin2) - - then: - if (key1 == key2 && value1 == value2 && origin1 == origin2) { - assert cs1.hashCode() == cs2.hashCode() - assert cs1 == cs2 - assert cs2 == cs1 - assert cs1.toString() == cs2.toString() - } else { - assert cs1.hashCode() != cs2.hashCode() - assert cs1 != cs2 - assert cs2 != cs1 - assert cs1.toString() != cs2.toString() - } - - where: - key1 | key2 | value1 | value2 | origin1 | origin2 - "key" | "key" | "value" | "value" | ConfigOrigin.DEFAULT | ConfigOrigin.DEFAULT - "key" | "key2" | "value" | "value" | ConfigOrigin.ENV | ConfigOrigin.ENV - "key" | "key" | "value2" | "value" | ConfigOrigin.JVM_PROP | ConfigOrigin.JVM_PROP - "key" | "key" | "value" | "value" | ConfigOrigin.ENV | ConfigOrigin.DEFAULT - } - - def "filters key values"() { - expect: - ConfigSetting.of(key, value, ConfigOrigin.DEFAULT).stringValue() == filteredValue - - where: - key | value | filteredValue - "DD_API_KEY" | "somevalue" | "" - "dd.api-key" | "somevalue" | "" - "dd.profiling.api-key" | "somevalue" | "" - "dd.profiling.apikey" | "somevalue" | "" - "some.other.key" | "somevalue" | "somevalue" - } - - def "support basic types"() { - expect: - ConfigSetting.of("key", value, ConfigOrigin.DEFAULT).stringValue() == rendered - - where: - value | rendered - null | null - true | "true" - false | "false" - 1 | "1" - 1.0 | "1.0" - 2.33f | "2.33" - "string" | "string" - } - - def "convert Iterable, Map, and BitSet to String"() { - expect: - ConfigSetting.of("key", value, ConfigOrigin.DEFAULT).stringValue() == rendered - - where: - value | rendered - ["1", "2", "3"] | "1,2,3" - [1, 2, 3] | "1,2,3" - [1.0f, 22.23d, 3.1415] | "1.0,22.23,3.1415" - [a: 1, b: 2] | "a:1,b:2" - [a: "1", b: "2"] | "a:1,b:2" - [:] | "" - [] | "" - bitSetIntervals() | "33,200-300,303,400-500" - } - - BitSet bitSetIntervals() { - def bitSetIntervals = new BitSet() - bitSetIntervals.set(33) - bitSetIntervals.set(200, 300) - bitSetIntervals.set(303) - bitSetIntervals.set(400, 500) - return bitSetIntervals - } -} diff --git a/utils/config-utils/src/test/groovy/datadog/trace/api/env/CapturedEnvironmentTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/api/env/CapturedEnvironmentTest.groovy deleted file mode 100644 index 78c52635898..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/api/env/CapturedEnvironmentTest.groovy +++ /dev/null @@ -1,151 +0,0 @@ -package datadog.trace.api.env - -import static java.io.File.separator - -import datadog.trace.api.config.GeneralConfig -import datadog.trace.test.util.DDSpecification - -class CapturedEnvironmentTest extends DDSpecification { - def "non autodetected service.name with null command"() { - when: - def serviceName = forkAndRunProperties('null') - - then: - serviceName == null - } - - def "non autodetected service.name with empty command"() { - when: - def serviceName = forkAndRunProperties('') - - then: - serviceName == null - } - - def "non autodetected service.name with all blanks command"() { - when: - def serviceName = forkAndRunProperties(' ') - - then: - serviceName == null - } - - def "set service.name by sysprop 'sun.java.command' with class"() { - when: - def serviceName = forkAndRunProperties('org.example.App -Dfoo=bar arg2 arg3') - - then: - serviceName == 'org.example.App' - } - - def "set service.name by sysprop 'sun.java.command' with jar"() { - when: - def serviceName = forkAndRunProperties('foo/bar/example.jar -Dfoo=bar arg2 arg3') - - then: - serviceName == 'example' - } - - def "set service.name with real 'sun.java.command' property"() { - when: - def serviceName = forkAndRunProperties(null) - - then: - serviceName == ServiceNamePrinter.name - } - - def "use Azure site name in Azure"() { - when: - def serviceName = forkAndRunProperties('foo/bar/example.jar -Dfoo=bar arg2 arg3', [ - 'DD_AZURE_APP_SERVICES': '1', - 'WEBSITE_SITE_NAME': 'siteService' - ]) - - then: - serviceName == 'siteService' - } - - def "dont use site name when not in azure"() { - when: - def serviceName = forkAndRunProperties('foo/bar/example.jar -Dfoo=bar arg2 arg3', [ - 'WEBSITE_SITE_NAME': 'siteService' - ]) - - then: - serviceName == 'example' - } - - def "dont use Azure site name when null"() { - when: - def serviceName = forkAndRunProperties('foo/bar/example.jar -Dfoo=bar arg2 arg3', [ - 'DD_AZURE_APP_SERVICES': 'true', - ]) - - then: - serviceName == 'example' - } - - private static String forkAndRunProperties(String arg, Map envVars = [:]) - throws IOException, InterruptedException { - // Build the command to run a new Java process - List command = [] - command += System.getProperty("java.home") + separator + "bin" + separator + "java" - command += '-cp' - command += System.getProperty("java.class.path") - command += ServiceNamePrinter.name - if (arg != null) { - command += arg - } - // Start the process - ProcessBuilder processBuilder = new ProcessBuilder(command) - processBuilder.environment().putAll(envVars) - Process process = processBuilder.start() - // Read and parse output and error streams - String serviceName = '' - try (BufferedReader reader = - new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line - while ((line = reader.readLine()) != null) { - if (serviceName != '') { - serviceName += '\n' - } - serviceName += line - } - } - if (serviceName == 'null') { - serviceName = null - } - String error = '' - try (BufferedReader reader = - new BufferedReader(new InputStreamReader(process.getErrorStream()))) { - String line - while ((line = reader.readLine()) != null) { - error += line + '\n' - } - } - // Wait for the process to complete - int exitCode = process.waitFor() - // Dumping state on error - if (exitCode != 0) { - println("Error printing service name. Exit code $exitCode with service name: '$serviceName' and error:\n$error") - throw new IllegalStateException('Process should exit without error') - } - return serviceName - } - - static class ServiceNamePrinter { - static void main(String[] args) { - if (args.length > 0) { - def sunJavaCommand = args[0] - if (sunJavaCommand == 'null') { - System.clearProperty('sun.java.command') - } else { - System.setProperty('sun.java.command', sunJavaCommand) - } - } - def capturedEnv = CapturedEnvironment.get() - def props = capturedEnv.properties - println props.get(GeneralConfig.SERVICE_NAME) - } - } -} diff --git a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.groovy deleted file mode 100644 index 1833f785eba..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.groovy +++ /dev/null @@ -1,18 +0,0 @@ -package datadog.trace.bootstrap.config.provider - -import datadog.trace.test.util.DDSpecification - -class AgentArgsInjectorTest extends DDSpecification { - - def "injects agent arguments as system properties"() { - given: - def agentArgs = "arg1=value1,arg2=value2" - - when: - AgentArgsInjector.injectAgentArgsConfig(agentArgs) - - then: - System.getProperty("arg1") == "value1" - System.getProperty("arg2") == "value2" - } -} diff --git a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.groovy deleted file mode 100644 index 83cd29701fd..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.groovy +++ /dev/null @@ -1,79 +0,0 @@ -package datadog.trace.bootstrap.config.provider - - -import spock.lang.Specification - -class AgentArgsParserTest extends Specification { - - def "parses a single argument"() { - given: - def args = "key1=value1" - - when: - def properties = AgentArgsParser.parseAgentArgs(args) - - then: - properties != null - properties.size() == 1 - properties.get("key1") == "value1" - } - - def "parses multiple arguments"() { - given: - def args = "key1=value1,key2=value2" - - when: - def properties = AgentArgsParser.parseAgentArgs(args) - - then: - properties != null - properties.size() == 2 - properties.get("key2") == "value2" - } - - def "returns null for null string"() { - given: - def args = null - - when: - def properties = AgentArgsParser.parseAgentArgs(args) - - then: - properties == null - } - - def "returns null for empty string"() { - given: - def args = "" - - when: - def properties = AgentArgsParser.parseAgentArgs(args) - - then: - properties == null - } - - def "returns null for malformed string"() { - given: - def args = "key=value,,,==" - - when: - def properties = AgentArgsParser.parseAgentArgs(args) - - then: - properties == null - } - - def "parses argument with spaces"() { - given: - def args = "key=value with spaces" - - when: - def properties = AgentArgsParser.parseAgentArgs(args) - - then: - properties != null - properties.size() == 1 - properties.get("key") == "value with spaces" - } -} diff --git a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigConverterTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigConverterTest.groovy deleted file mode 100644 index 0f9e1dd752d..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigConverterTest.groovy +++ /dev/null @@ -1,195 +0,0 @@ -package datadog.trace.bootstrap.config.provider - -import datadog.trace.test.util.DDSpecification - -class ConfigConverterTest extends DDSpecification { - - def "Convert boolean properties"() { - when: - def value = ConfigConverter.valueOf(stringValue, Boolean) - - then: - value == expectedConvertedValue - - where: - stringValue | expectedConvertedValue - "true" | true - "TRUE" | true - "True" | true - "1" | true - "false" | false - null | null - "" | null - "0" | false - } - - def "Convert boolean properties throws exception for invalid values"() { - when: - ConfigConverter.valueOf(invalidValue, Boolean) - - then: - def exception = thrown(ConfigConverter.InvalidBooleanValueException) - exception.message.contains("Invalid boolean value:") - - where: - invalidValue << [ - "42.42", - "tru", - "truee", - "true ", - " true", - " true ", - " true ", - "notABool", - "yes", - "no", - "on", - "off" - ] - } - - def "parse map properly for #mapString"() { - when: - def result = ConfigConverter.parseMap(mapString, "test") - - then: - result == expected - - where: - // spotless:off - mapString | expected - "a:1, a:2, a:3" | [a: "3"] - "a:b,c:d,e:" | [a: "b", c: "d"] - // space separated - "a:1 a:2 a:3" | [a: "3"] - "a:b c:d e:" | [a: "b", c: "d"] - // More different string variants: - "a:a;" | [a: "a;"] - "a:1, a:2, a:3" | [a: "3"] - "a:1 a:2 a:3" | [a: "3"] - "a:b,c:d,e:" | [a: "b", c: "d"] - "a:b c:d e:" | [a: "b", c: "d"] - "key 1!:va|ue_1," | ["key 1!": "va|ue_1"] - "key 1!:va|ue_1 " | ["key 1!": "va|ue_1"] - " key1 :value1 ,\t key2: value2" | [key1: "value1", key2: "value2"] - 'a:b, b:c, c:d, d: e' | ['a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'] - "key1 :value1 \t key2: value2" | [key1: "value1", key2: "value2"] - "dyno:web.1 dynotype:web appname:******" | ["dyno": "web.1", "dynotype": "web", "appname": "******"] - "is:val:id" | [is: "val:id"] - "a:b,is:val:id,x:y" | [a: "b", is: "val:id", x: "y"] - "a:b:c:d" | [a: "b:c:d"] - 'fooa:barb, foob:barc, fooc: bard, food: bare,' | ['fooa': 'barb', 'foob': 'barc', 'fooc': 'bard', 'food': 'bare'] - "a:b=c=d" | [a: "b=c=d"] - // Illegal - "a:" | [:] - "a:b,c,d" | [:] - "a:b,c,d,k:v" | [:] - "" | [:] - "1" | [:] - "a" | [:] - "a,1" | [:] - "!a" | [:] - " " | [:] - ",,,," | [:] - ":,:,:,:," | [:] - ": : : : " | [:] - "::::" | [:] - 'key1:val1 with_space:and_colon, key2:val2' | [:] - // spotless:on - } - - def "parse map for #mapString with separator #separator"() { - when: - def result = ConfigConverter.parseMap(mapString, "test", separator as char) - - then: - result == expected - - where: - // spotless:off - mapString | separator | expected - "a=1, a=2, a=3" | '=' | [a: "3"] - "a=b,c=d,e=" | '=' | [a: "b", c: "d"] - "a;b,c;d,e;" | ';' | [a: "b", c: "d"] - // space separated - "a=1 a=2 a=3" | '=' | [a: "3"] - "a=b c=d e=" | '=' | [a: "b", c: "d"] - // More different string variants - "a=b=c=d" | '=' | [a: "b=c=d"] - 'fooa=barb, foob=barc, fooc= bard, food= bare,' | '=' | ['fooa': 'barb', 'foob': 'barc', 'fooc': 'bard', 'food': 'bare'] - "a=b:c:d" | '=' | [a: "b:c:d"] - // Illegal - "a=" | '=' | [:] - "====" | '=' | [:] - // spotless:on - } - - def "parsing map #mapString with List of arg separators for with key value separator #separator"() { - //testing parsing for DD_TAGS - setup: - def separatorList = [',' as char, ' ' as char] - - when: - def result = ConfigConverter.parseTraceTagsMap(mapString, separator as char, separatorList as List) - - then: - result == expected - - where: - // spotless:off - mapString | separator | expected - "key1:value1,key2:value2" | ':' | [key1: "value1", key2: "value2"] - "key1:value1 key2:value2" | ':' | [key1: "value1", key2: "value2"] - "env:test aKey:aVal bKey:bVal cKey:" | ':' | [env: "test", aKey: "aVal", bKey: "bVal", cKey:""] - "env:test,aKey:aVal,bKey:bVal,cKey:" | ':' | [env: "test", aKey: "aVal", bKey: "bVal", cKey:""] - "env:test,aKey:aVal bKey:bVal cKey:" | ':' | [env: "test", aKey: "aVal bKey:bVal cKey:"] - "env:test bKey :bVal dKey: dVal cKey:" | ':' | [env: "test", bKey: "", dKey: "", dVal: "", cKey: ""] - 'env :test, aKey : aVal bKey:bVal cKey:' | ':' | [env: "test", aKey : "aVal bKey:bVal cKey:"] - "env:keyWithA:Semicolon bKey:bVal cKey" | ':' | [env: "keyWithA:Semicolon", bKey: "bVal", cKey: ""] - "env:keyWith: , , Lots:Of:Semicolons " | ':' | [env: "keyWith:", Lots: "Of:Semicolons"] - "a:b,c,d" | ':' | [a: "b", c: "", d: ""] - "a,1" | ':' | [a: "", "1": ""] - "a:b:c:d" | ':' | [a: "b:c:d"] - //edge cases - "noDelimiters" | ':' | [noDelimiters: ""] - " " | ':' | [:] - ",,,,,,,,,,,," | ':' | [:] - ", , , , , , " | ':' | [:] - // spotless:on - } - - def "test parseMapWithOptionalMappings"() { - when: - def result = ConfigConverter.parseMapWithOptionalMappings(mapString, "test", defaultPrefix, lowercaseKeys) - - then: - result == expected - - where: - mapString | expected | lowercaseKeys | defaultPrefix - "header1:one,header2:two" | [header1: "one", header2: "two"] | false | "" - "header1:one, header2:two" | [header1: "one", header2: "two"] | false | "" - "header1,header2:two" | [header1: "header1", header2: "two"] | false | "" - "Header1:one,header2:two" | [header1: "one", header2: "two"] | true | "" - "\"header1:one,header2:two\"" | ["\"header1": "one", header2: "two\""] | true | "" - "header1" | [header1: "header1"] | true | "" - ",header1:tag" | [header1: "tag"] | true | "" - "header1:tag," | [header1: "tag"] | true | "" - "header:tag:value" | [header: "tag:value"] | true | "" - "" | [:] | true | "" - null | [:] | true | "" - // Test for wildcard header tags - "*" | ["*":"datadog.response.headers."] | true | "datadog.response.headers" - "*:" | [:] | true | "datadog.response.headers" - "*,header1:tag" | ["*":"datadog.response.headers."] | true | "datadog.response.headers" - "header1:tag,*" | ["*":"datadog.response.headers."] | true | "datadog.response.headers" - // logs warning: Illegal key only tag starting with non letter '1header' - "1header,header2:two" | [:] | true | "" - // logs warning: Illegal tag starting with non letter for key 'header' - "header::tag" | [:] | true | "" - // logs warning: Illegal empty key at position 0 - ":tag" | [:] | true | "" - // logs warning: Illegal empty key at position 11 - "header:tag,:tag" | [:] | true | "" - } -} diff --git a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.groovy deleted file mode 100644 index b8a4a15757c..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package datadog.trace.bootstrap.config.provider - -import datadog.trace.test.util.DDSpecification - -class PropertiesConfigSourceTest extends DDSpecification { - - def "test null"() { - when: - new PropertiesConfigSource(null, true) - - then: - thrown(AssertionError) - } - - def "config pulled from properties"() { - setup: - def props = new Properties(["abc": "def", "dd.abc": "xyz"]) - def source = new PropertiesConfigSource(props, false) - - expect: - source.get("abc") == "def" - source.get("dd.abc") == "xyz" - source.get("missing") == null - } - - def "config pulled from properties with prefix"() { - setup: - def props = new Properties(["abc": "def", "dd.abc": "xyz"]) - def source = new PropertiesConfigSource(props, true) - - expect: - source.get("abc") == "xyz" - source.get("dd.abc") == null - source.get("missing") == null - } -} diff --git a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.groovy deleted file mode 100644 index 2a1af178562..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.groovy +++ /dev/null @@ -1,35 +0,0 @@ -package datadog.trace.bootstrap.config.provider - -import datadog.trace.bootstrap.config.provider.stableconfig.StableConfigMappingException -import spock.lang.Specification - -class StableConfigMappingExceptionTest extends Specification { - - def "constructors work as expected"() { - when: - def ex1 = new StableConfigMappingException("msg") - def ex2 = new StableConfigMappingException("msg2") - - then: - ex1.message == "msg" - ex1.cause == null - ex2.message == "msg2" - } - - def "safeToString handles null"() { - expect: - StableConfigMappingException.safeToString(null) == "null" - } - - def "safeToString handles short string"() { - expect: - StableConfigMappingException.safeToString("short string") == "short string" - } - - def "safeToString handles long string"() { - given: - def longStr = "a" * 101 - expect: - StableConfigMappingException.safeToString(longStr) == ("a" * 50) + "...(truncated)..." + ("a" * 51).substring(1) - } -} diff --git a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy b/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy deleted file mode 100644 index 84aeda8ebb0..00000000000 --- a/utils/config-utils/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy +++ /dev/null @@ -1,322 +0,0 @@ -package datadog.trace.bootstrap.config.provider - -import datadog.trace.api.ConfigCollector - -import static java.util.Collections.singletonMap - -import datadog.trace.api.ConfigOrigin -import datadog.trace.bootstrap.config.provider.stableconfig.Rule -import datadog.trace.bootstrap.config.provider.stableconfig.Selector -import datadog.trace.bootstrap.config.provider.stableconfig.StableConfig -import datadog.trace.test.util.DDSpecification -import org.snakeyaml.engine.v2.api.Dump -import org.snakeyaml.engine.v2.api.DumpSettings -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.read.ListAppender -import org.slf4j.LoggerFactory - -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardOpenOption - -class StableConfigSourceTest extends DDSpecification { - - def "test file doesn't exist"() { - setup: - StableConfigSource config = new StableConfigSource(StableConfigSource.LOCAL_STABLE_CONFIG_PATH, ConfigOrigin.LOCAL_STABLE_CONFIG) - - expect: - config.getKeys().size() == 0 - config.getConfigId() == null - } - - def "test empty file"() { - given: - Path filePath = Files.createTempFile("testFile_", ".yaml") - - when: - StableConfigSource config = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) - then: - config.getKeys().size() == 0 - config.getConfigId() == null - - cleanup: - Files.delete(filePath) - } - - def "test file invalid format"() { - // StableConfigSource must handle the exception thrown by StableConfigParser.parse(filePath) gracefully - when: - Path filePath = Files.createTempFile("testFile_", ".yaml") - then: - if (filePath == null) { - throw new AssertionError("Failed to create: " + filePath) - } - - when: - writeFileRaw(filePath, configId, data) - StableConfigSource stableCfg = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) - - then: - stableCfg.getConfigId() == null - stableCfg.getKeys().size() == 0 - - cleanup: - Files.delete(filePath) - - where: - configId | data - null | corruptYaml - "12345" | "this is not yaml format!" - } - - def "test null values in YAML"() { - when: - Path filePath = Files.createTempFile("testFile_", ".yaml") - then: - if (filePath == null) { - throw new AssertionError("Failed to create: " + filePath) - } - - when: - // Test the scenario where YAML contains null values for apm_configuration_default and apm_configuration_rules - String yaml = """ -config_id: "12345" -apm_configuration_default: -apm_configuration_rules: -""" - Files.write(filePath, yaml.getBytes()) - StableConfigSource stableCfg = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) - - then: - // Should not throw NullPointerException and should handle null values gracefully - stableCfg.getConfigId() == "12345" - stableCfg.getKeys().size() == 0 - Files.delete(filePath) - } - - def "test file valid format"() { - given: - Path filePath = Files.createTempFile("testFile_", ".yaml") - - when: - StableConfig stableConfigYaml = new StableConfig(configId, defaultConfigs) - writeFileYaml(filePath, stableConfigYaml) - StableConfigSource stableCfg = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) - - then: - for (key in defaultConfigs.keySet()) { - String keyString = (String) key - keyString = keyString.substring(4) // Cut `DD_` - stableCfg.get(keyString) == defaultConfigs.get(key) - } - // All configs from MatchingRule should be applied - if (ruleConfigs.contains(sampleMatchingRule)) { - for (key in sampleMatchingRule.getConfiguration().keySet()) { - String keyString = (String) key - keyString = keyString.substring(4) // Cut `DD_` - stableCfg.get(keyString) == defaultConfigs.get(key) - } - } - // None of the configs from NonMatchingRule should be applied - if (ruleConfigs.contains(sampleNonMatchingRule)) { - Set cfgKeys = stableCfg.getKeys() - for (key in sampleMatchingRule.getConfiguration().keySet()) { - String keyString = (String) key - keyString = keyString.substring(4) // Cut `DD_` - !cfgKeys.contains(keyString) - } - } - - cleanup: - Files.delete(filePath) - - where: - configId | defaultConfigs | ruleConfigs - "" | [:] | Arrays.asList(new Rule()) - "12345" | ["DD_KEY_ONE": "one", "DD_KEY_TWO": "two"] | Arrays.asList(sampleMatchingRule, sampleNonMatchingRule) - } - - def "test parse invalid logs mapping errors"() { - given: - Logger logbackLogger = (Logger) LoggerFactory.getLogger(StableConfigSource) - def listAppender = new ListAppender() - listAppender.start() - logbackLogger.addAppender(listAppender) - - def tempFile = File.createTempFile("testFile_", ".yaml") - tempFile.text = yaml - - when: - def stableCfg = new StableConfigSource(tempFile.absolutePath, ConfigOrigin.LOCAL_STABLE_CONFIG) - - then: - stableCfg.config == StableConfigSource.StableConfig.EMPTY - def warnLogs = listAppender.list.findAll { it.level.toString() == 'WARN' } - warnLogs.any { it.formattedMessage.contains(expectedLogSubstring) } - - cleanup: - tempFile.delete() - logbackLogger.detachAppender(listAppender) - - where: - yaml | expectedLogSubstring - '''apm_configuration_rules: - - selectors: - - key: "someKey" - matches: ["someValue"] - operator: equals - configuration: - DD_SERVICE: "test" - ''' | "Missing 'origin' in selector" - '''apm_configuration_rules: - - selectors: - - origin: process_arguments - key: "-Dfoo" - matches: ["bar"] - operator: equals - ''' | "Missing 'configuration' in rule" - '''apm_configuration_rules: - - configuration: - DD_SERVICE: "test" - ''' | "Missing 'selectors' in rule" - '''apm_configuration_rules: - - selectors: "not-a-list" - configuration: - DD_SERVICE: "test" - ''' | "'selectors' must be a list, but got: String" - '''apm_configuration_rules: - - selectors: - - "not-a-map" - configuration: - DD_SERVICE: "test" - ''' | "Each selector must be a map, but got: String" - '''apm_configuration_rules: - - selectors: - - origin: process_arguments - key: "-Dfoo" - matches: ["bar"] - operator: equals - configuration: "not-a-map" - ''' | "'configuration' must be a map, but got: String" - '''apm_configuration_rules: - - selectors: - - origin: process_arguments - key: "-Dfoo" - matches: ["bar"] - operator: equals - configuration: 12345 - ''' | "'configuration' must be a map, but got: Integer" - '''apm_configuration_rules: - - "not-a-map" - ''' | "Rule must be a map, but got: String" - '''apm_configuration_rules: - - selectors: - - origin: process_arguments - key: "-Dfoo" - matches: "not-a-list" - operator: equals - configuration: - DD_SERVICE: "test" - ''' | "'matches' must be a list, but got: String" - '''apm_configuration_rules: - - selectors: - - origin: process_arguments - key: "-Dfoo" - matches: ["bar"] - configuration: - DD_SERVICE: "test" - ''' | "Missing 'operator' in selector" - '''apm_configuration_rules: - - selectors: - - origin: process_arguments - key: "-Dfoo" - matches: ["bar"] - operator: 12345 - configuration: - DD_SERVICE: "test" - ''' | "'operator' must be a string, but got: Integer" - '''apm_configuration_rules: - - selectors: - # origin is missing entirely, should trigger NullPointerException - - key: "-Dfoo" - matches: ["bar"] - operator: equals - ''' | "YAML mapping error in stable configuration file" - } - - // Corrupt YAML string variable used for testing, defined outside the 'where' block for readability - static corruptYaml = ''' - abc: 123 - def: - ghi: "jkl" - lmn: 456 - ''' - - // Matching and non-matching Rules used for testing, defined outside the 'where' block for readability - static sampleMatchingRule = new Rule(Arrays.asList(new Selector("origin", "language", Arrays.asList("Java"), null)), singletonMap("DD_KEY_THREE", "three")) - static sampleNonMatchingRule = new Rule(Arrays.asList(new Selector("origin", "language", Arrays.asList("Golang"), null)), singletonMap("DD_KEY_FOUR", "four")) - - def writeFileYaml(Path filePath, StableConfig stableConfigs) { - Map yamlData = new HashMap<>() - - if (stableConfigs.getConfigId() != null && !stableConfigs.getConfigId().isEmpty()) { - yamlData.put("config_id", stableConfigs.getConfigId()) - } - - if (stableConfigs.getApmConfigurationDefault() != null && !stableConfigs.getApmConfigurationDefault().isEmpty()) { - yamlData.put("apm_configuration_default", stableConfigs.getApmConfigurationDefault()) - } - - DumpSettings settings = DumpSettings.builder().build() - Dump dump = new Dump(settings) - String yamlContent = dump.dumpToString(yamlData) - - try (FileWriter writer = new FileWriter(filePath.toFile())) { - writer.write(yamlContent) - } - } - - def "test config id exists in ConfigCollector when using StableConfigSource"() { - given: - Path filePath = Files.createTempFile("testFile_", ".yaml") - String expectedConfigId = "123" - - // Create YAML content with config_id and some configuration - def yamlContent = """ -config_id: ${expectedConfigId} -apm_configuration_default: - DD_SERVICE: test-service - DD_ENV: test-env -""" - Files.write(filePath, yamlContent.getBytes()) - - // Clear any existing collected config - ConfigCollector.get().collect().clear() - - when: - // Create StableConfigSource and ConfigProvider - StableConfigSource stableConfigSource = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) - ConfigProvider configProvider = new ConfigProvider(stableConfigSource) - - // Trigger config collection by getting a value - configProvider.getString("SERVICE", "default-service") - - then: - def collectedConfigs = ConfigCollector.get().collect() - def serviceSetting = collectedConfigs.get(ConfigOrigin.LOCAL_STABLE_CONFIG).("SERVICE") - serviceSetting.configId == expectedConfigId - serviceSetting.value == "test-service" - serviceSetting.origin == ConfigOrigin.LOCAL_STABLE_CONFIG - - cleanup: - Files.delete(filePath) - } - - def writeFileRaw(Path filePath, String configId, String data) { - data = configId + "\n" + data - StandardOpenOption[] openOpts = [StandardOpenOption.WRITE] as StandardOpenOption[] - Files.write(filePath, data.getBytes(), openOpts) - } -} diff --git a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java new file mode 100644 index 00000000000..09d874eff5b --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java @@ -0,0 +1,119 @@ +package datadog.trace.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.tabletest.junit.TableTest; + +public class ConfigSettingTest { + + @ParameterizedTest + @MethodSource("supportsEqualityCheckArguments") + void supportsEqualityCheck( + String key1, + String key2, + Object value1, + Object value2, + ConfigOrigin origin1, + ConfigOrigin origin2, + boolean expectedEqual) { + // when + ConfigSetting cs1 = ConfigSetting.of(key1, value1, origin1); + ConfigSetting cs2 = ConfigSetting.of(key2, value2, origin2); + + // then + if (expectedEqual) { + assertEquals(cs1.hashCode(), cs2.hashCode()); + assertEquals(cs1, cs2); + assertEquals(cs2, cs1); + assertEquals(cs1.toString(), cs2.toString()); + } else { + assertNotEquals(cs1.hashCode(), cs2.hashCode()); + assertNotEquals(cs1, cs2); + assertNotEquals(cs2, cs1); + assertNotEquals(cs1.toString(), cs2.toString()); + } + } + + static Stream supportsEqualityCheckArguments() { + return Stream.of( + arguments("key", "key", "value", "value", ConfigOrigin.DEFAULT, ConfigOrigin.DEFAULT, true), + arguments("key", "key2", "value", "value", ConfigOrigin.ENV, ConfigOrigin.ENV, false), + arguments( + "key", "key", "value2", "value", ConfigOrigin.JVM_PROP, ConfigOrigin.JVM_PROP, false), + arguments("key", "key", "value", "value", ConfigOrigin.ENV, ConfigOrigin.DEFAULT, false)); + } + + @TableTest({ + "scenario | key | value | filteredValue", + "DD_API_KEY | DD_API_KEY | somevalue | ", + "dd.api-key | dd.api-key | somevalue | ", + "dd.profiling.api-key | dd.profiling.api-key | somevalue | ", + "dd.profiling.apikey | dd.profiling.apikey | somevalue | ", + "some.other.key | some.other.key | somevalue | somevalue " + }) + void filtersKeyValues(String key, String value, String filteredValue) { + assertEquals(filteredValue, ConfigSetting.of(key, value, ConfigOrigin.DEFAULT).stringValue()); + } + + @ParameterizedTest + @MethodSource("supportBasicTypesArguments") + void supportBasicTypes(Object value, String rendered) { + assertEquals(rendered, ConfigSetting.of("key", value, ConfigOrigin.DEFAULT).stringValue()); + } + + static Stream supportBasicTypesArguments() { + return Stream.of( + arguments((Object) null, (String) null), + arguments(true, "true"), + arguments(false, "false"), + arguments(1, "1"), + arguments(1.0d, "1.0"), + arguments(2.33f, "2.33"), + arguments("string", "string")); + } + + @ParameterizedTest + @MethodSource("convertIterableMapAndBitSetArguments") + void convertIterableMapAndBitSetToString(Object value, String rendered) { + assertEquals(rendered, ConfigSetting.of("key", value, ConfigOrigin.DEFAULT).stringValue()); + } + + static Stream + convertIterableMapAndBitSetArguments() { + Map mapStringInt = new LinkedHashMap<>(); + mapStringInt.put("a", 1); + mapStringInt.put("b", 2); + + Map mapStringString = new LinkedHashMap<>(); + mapStringString.put("a", "1"); + mapStringString.put("b", "2"); + + return Stream.of( + arguments(Arrays.asList("1", "2", "3"), "1,2,3"), + arguments(Arrays.asList(1, 2, 3), "1,2,3"), + arguments(Arrays.asList(1.0f, 22.23d, 3.1415d), "1.0,22.23,3.1415"), + arguments(mapStringInt, "a:1,b:2"), + arguments(mapStringString, "a:1,b:2"), + arguments(new LinkedHashMap<>(), ""), + arguments(Arrays.asList(), ""), + arguments(bitSetIntervals(), "33,200-300,303,400-500")); + } + + private static BitSet bitSetIntervals() { + BitSet bitSet = new BitSet(); + bitSet.set(33); + bitSet.set(200, 300); + bitSet.set(303); + bitSet.set(400, 500); + return bitSet; + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/api/env/CapturedEnvironmentTest.java b/utils/config-utils/src/test/java/datadog/trace/api/env/CapturedEnvironmentTest.java new file mode 100644 index 00000000000..0e704f81d2e --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/api/env/CapturedEnvironmentTest.java @@ -0,0 +1,168 @@ +package datadog.trace.api.env; + +import static java.io.File.separator; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import datadog.trace.api.config.GeneralConfig; +import datadog.trace.test.util.DDJavaSpecification; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class CapturedEnvironmentTest extends DDJavaSpecification { + + @Test + void nonAutodetectedServiceNameWithNullCommand() throws IOException, InterruptedException { + String serviceName = forkAndRunProperties("null"); + + assertNull(serviceName); + } + + @Test + void nonAutodetectedServiceNameWithEmptyCommand() throws IOException, InterruptedException { + String serviceName = forkAndRunProperties(""); + + assertNull(serviceName); + } + + @Test + void nonAutodetectedServiceNameWithAllBlanksCommand() throws IOException, InterruptedException { + String serviceName = forkAndRunProperties(" "); + + assertNull(serviceName); + } + + @Test + void setServiceNameBySyspropSunJavaCommandWithClass() throws IOException, InterruptedException { + String serviceName = forkAndRunProperties("org.example.App -Dfoo=bar arg2 arg3"); + + assertEquals("org.example.App", serviceName); + } + + @Test + void setServiceNameBySyspropSunJavaCommandWithJar() throws IOException, InterruptedException { + String serviceName = forkAndRunProperties("foo/bar/example.jar -Dfoo=bar arg2 arg3"); + + assertEquals("example", serviceName); + } + + @Test + void setServiceNameWithRealSunJavaCommandProperty() throws IOException, InterruptedException { + String serviceName = forkAndRunProperties(null); + + assertEquals(ServiceNamePrinter.class.getName(), serviceName); + } + + @Test + void useAzureSiteNameInAzure() throws IOException, InterruptedException { + HashMap azureEnvVars = new HashMap<>(); + azureEnvVars.put("DD_AZURE_APP_SERVICES", "1"); + azureEnvVars.put("WEBSITE_SITE_NAME", "siteService"); + + String serviceName = + forkAndRunProperties("foo/bar/example.jar -Dfoo=bar arg2 arg3", azureEnvVars); + + assertEquals("siteService", serviceName); + } + + @Test + void dontUseSiteNameWhenNotInAzure() throws IOException, InterruptedException { + String serviceName = + forkAndRunProperties( + "foo/bar/example.jar -Dfoo=bar arg2 arg3", + Collections.singletonMap("WEBSITE_SITE_NAME", "siteService")); + + assertEquals("example", serviceName); + } + + @Test + void dontUseAzureSiteNameWhenNull() throws IOException, InterruptedException { + String serviceName = + forkAndRunProperties( + "foo/bar/example.jar -Dfoo=bar arg2 arg3", + Collections.singletonMap("DD_AZURE_APP_SERVICES", "true")); + + assertEquals("example", serviceName); + } + + private static String forkAndRunProperties(String arg) throws IOException, InterruptedException { + return forkAndRunProperties(arg, Collections.emptyMap()); + } + + private static String forkAndRunProperties(String arg, Map envVars) + throws IOException, InterruptedException { + // Build the command to run a new Java process + List command = new ArrayList<>(); + command.add(System.getProperty("java.home") + separator + "bin" + separator + "java"); + command.add("-cp"); + command.add(System.getProperty("java.class.path")); + command.add(ServiceNamePrinter.class.getName()); + if (arg != null) { + command.add(arg); + } + // Start the process + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.environment().putAll(envVars); + Process process = processBuilder.start(); + // Read and parse output and error streams + String serviceName = ""; + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + if (!serviceName.isEmpty()) { + serviceName += "\n"; + } + serviceName += line; + } + } + if ("null".equals(serviceName)) { + serviceName = null; + } + String error = ""; + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + error += line + "\n"; + } + } + // Wait for the process to complete + int exitCode = process.waitFor(); + // Dumping state on error + if (exitCode != 0) { + System.out.println( + "Error printing service name. Exit code " + + exitCode + + " with service name: '" + + serviceName + + "' and error:\n" + + error); + throw new IllegalStateException("Process should exit without error"); + } + return serviceName; + } + + public static class ServiceNamePrinter { + public static void main(String[] args) { + if (args.length > 0) { + String sunJavaCommand = args[0]; + if ("null".equals(sunJavaCommand)) { + System.clearProperty("sun.java.command"); + } else { + System.setProperty("sun.java.command", sunJavaCommand); + } + } + CapturedEnvironment capturedEnv = CapturedEnvironment.get(); + Map props = capturedEnv.getProperties(); + System.out.println(props.get(GeneralConfig.SERVICE_NAME)); + } + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.java new file mode 100644 index 00000000000..9c762f96bbc --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsInjectorTest.java @@ -0,0 +1,26 @@ +package datadog.trace.bootstrap.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import datadog.trace.test.util.DDJavaSpecification; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class AgentArgsInjectorTest extends DDJavaSpecification { + + @AfterEach + void clearInjectedProperties() { + System.clearProperty("arg1"); + System.clearProperty("arg2"); + } + + @Test + void injectsAgentArgumentsAsSystemProperties() { + String agentArgs = "arg1=value1,arg2=value2"; + + AgentArgsInjector.injectAgentArgsConfig(agentArgs); + + assertEquals("value1", System.getProperty("arg1")); + assertEquals("value2", System.getProperty("arg2")); + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.java new file mode 100644 index 00000000000..4c8575d81a1 --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/AgentArgsParserTest.java @@ -0,0 +1,65 @@ +package datadog.trace.bootstrap.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class AgentArgsParserTest { + + @Test + void parsesASingleArgument() { + String args = "key1=value1"; + + Map properties = AgentArgsParser.parseAgentArgs(args); + + assertNotNull(properties); + assertEquals(1, properties.size()); + assertEquals("value1", properties.get("key1")); + } + + @Test + void parsesMultipleArguments() { + String args = "key1=value1,key2=value2"; + + Map properties = AgentArgsParser.parseAgentArgs(args); + + assertNotNull(properties); + assertEquals(2, properties.size()); + assertEquals("value2", properties.get("key2")); + } + + @Test + void returnsNullForNullString() { + Map properties = AgentArgsParser.parseAgentArgs(null); + + assertNull(properties); + } + + @Test + void returnsNullForEmptyString() { + Map properties = AgentArgsParser.parseAgentArgs(""); + + assertNull(properties); + } + + @Test + void returnsNullForMalformedString() { + Map properties = AgentArgsParser.parseAgentArgs("key=value,,,=="); + + assertNull(properties); + } + + @Test + void parsesArgumentWithSpaces() { + String args = "key=value with spaces"; + + Map properties = AgentArgsParser.parseAgentArgs(args); + + assertNotNull(properties); + assertEquals(1, properties.size()); + assertEquals("value with spaces", properties.get("key")); + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java new file mode 100644 index 00000000000..82548f7498a --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java @@ -0,0 +1,232 @@ +package datadog.trace.bootstrap.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import datadog.trace.test.util.DDJavaSpecification; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class ConfigConverterTest extends DDJavaSpecification { + + @ParameterizedTest + @MethodSource("convertBooleanPropertiesArguments") + void convertBooleanProperties(String stringValue, Boolean expectedConvertedValue) { + assertEquals(expectedConvertedValue, ConfigConverter.valueOf(stringValue, Boolean.class)); + } + + static Stream convertBooleanPropertiesArguments() { + return Stream.of( + arguments("true", true), + arguments("TRUE", true), + arguments("True", true), + arguments("1", true), + arguments("false", false), + arguments((String) null, (Boolean) null), + arguments("", (Boolean) null), + arguments("0", false)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "42.42", + "tru", + "truee", + "true ", + " true", + " true ", + " true ", + "notABool", + "yes", + "no", + "on", + "off" + }) + void convertBooleanPropertiesThrowsExceptionForInvalidValues(String invalidValue) { + ConfigConverter.InvalidBooleanValueException exception = + assertThrows( + ConfigConverter.InvalidBooleanValueException.class, + () -> ConfigConverter.valueOf(invalidValue, Boolean.class)); + assertEquals(true, exception.getMessage().contains("Invalid boolean value:")); + } + + @ParameterizedTest + @MethodSource("parseMapProperlyArguments") + void parseMapProperly(String mapString, Map expected) { + // spotless:off + assertEquals(expected, ConfigConverter.parseMap(mapString, "test")); + // spotless:on + } + + static Stream parseMapProperlyArguments() { + return Stream.of( + // spotless:off + arguments("a:1, a:2, a:3", mapOf("a", "3")), + arguments("a:b,c:d,e:", mapOf("a", "b", "c", "d")), + // space separated + arguments("a:1 a:2 a:3", mapOf("a", "3")), + arguments("a:b c:d e:", mapOf("a", "b", "c", "d")), + // More different string variants: + arguments("a:a;", mapOf("a", "a;")), + arguments("a:1, a:2, a:3", mapOf("a", "3")), + arguments("a:1 a:2 a:3", mapOf("a", "3")), + arguments("a:b,c:d,e:", mapOf("a", "b", "c", "d")), + arguments("a:b c:d e:", mapOf("a", "b", "c", "d")), + arguments("key 1!:va|ue_1,", mapOf("key 1!", "va|ue_1")), + arguments("key 1!:va|ue_1 ", mapOf("key 1!", "va|ue_1")), + arguments(" key1 :value1 ,\t key2: value2", mapOf("key1", "value1", "key2", "value2")), + arguments("a:b, b:c, c:d, d: e", mapOf("a", "b", "b", "c", "c", "d", "d", "e")), + arguments("key1 :value1 \t key2: value2", mapOf("key1", "value1", "key2", "value2")), + arguments("dyno:web.1 dynotype:web appname:******", mapOf("dyno", "web.1", "dynotype", "web", "appname", "******")), + arguments("is:val:id", mapOf("is", "val:id")), + arguments("a:b,is:val:id,x:y", mapOf("a", "b", "is", "val:id", "x", "y")), + arguments("a:b:c:d", mapOf("a", "b:c:d")), + arguments("fooa:barb, foob:barc, fooc: bard, food: bare,", mapOf("fooa", "barb", "foob", "barc", "fooc", "bard", "food", "bare")), + arguments("a:b=c=d", mapOf("a", "b=c=d")), + // Illegal + arguments("a:", Collections.emptyMap()), + arguments("a:b,c,d", Collections.emptyMap()), + arguments("a:b,c,d,k:v", Collections.emptyMap()), + arguments("", Collections.emptyMap()), + arguments("1", Collections.emptyMap()), + arguments("a", Collections.emptyMap()), + arguments("a,1", Collections.emptyMap()), + arguments("!a", Collections.emptyMap()), + arguments(" ", Collections.emptyMap()), + arguments(",,,,", Collections.emptyMap()), + arguments(":,:,:,:", Collections.emptyMap()), + arguments(": : : : ", Collections.emptyMap()), + arguments("::::", Collections.emptyMap()), + arguments("key1:val1 with_space:and_colon, key2:val2", Collections.emptyMap()) + // spotless:on + ); + } + + @ParameterizedTest + @MethodSource("parseMapWithSeparatorArguments") + void parseMapWithSeparator(String mapString, char separator, Map expected) { + // spotless:off + assertEquals(expected, ConfigConverter.parseMap(mapString, "test", separator)); + // spotless:on + } + + static Stream parseMapWithSeparatorArguments() { + return Stream.of( + // spotless:off + arguments("a=1, a=2, a=3", '=', mapOf("a", "3")), + arguments("a=b,c=d,e=", '=', mapOf("a", "b", "c", "d")), + arguments("a;b,c;d,e;", ';', mapOf("a", "b", "c", "d")), + // space separated + arguments("a=1 a=2 a=3", '=', mapOf("a", "3")), + arguments("a=b c=d e=", '=', mapOf("a", "b", "c", "d")), + // More different string variants + arguments("a=b=c=d", '=', mapOf("a", "b=c=d")), + arguments("fooa=barb, foob=barc, fooc= bard, food= bare,", '=', mapOf("fooa", "barb", "foob", "barc", "fooc", "bard", "food", "bare")), + arguments("a=b:c:d", '=', mapOf("a", "b:c:d")), + // Illegal + arguments("a=", '=', Collections.emptyMap()), + arguments("====", '=', Collections.emptyMap()) + // spotless:on + ); + } + + @ParameterizedTest + @MethodSource("parseTraceTagsMapArguments") + void parsingMapWithListOfArgSeparatorsForWithKeyValueSeparator( + String mapString, char separator, Map expected) { + // testing parsing for DD_TAGS + List separatorList = Arrays.asList(',', ' '); + // spotless:off + assertEquals(expected, ConfigConverter.parseTraceTagsMap(mapString, separator, separatorList)); + // spotless:on + } + + static Stream parseTraceTagsMapArguments() { + return Stream.of( + // spotless:off + arguments("key1:value1,key2:value2", ':', mapOf("key1", "value1", "key2", "value2")), + arguments("key1:value1 key2:value2", ':', mapOf("key1", "value1", "key2", "value2")), + arguments("env:test aKey:aVal bKey:bVal cKey:", ':', mapOf("env", "test", "aKey", "aVal", "bKey", "bVal", "cKey", "")), + arguments("env:test,aKey:aVal,bKey:bVal,cKey:", ':', mapOf("env", "test", "aKey", "aVal", "bKey", "bVal", "cKey", "")), + arguments("env:test,aKey:aVal bKey:bVal cKey:", ':', mapOf("env", "test", "aKey", "aVal bKey:bVal cKey:")), + arguments("env:test bKey :bVal dKey: dVal cKey:", ':', mapOf("env", "test", "bKey", "", "dKey", "", "dVal", "", "cKey", "")), + arguments("env :test, aKey : aVal bKey:bVal cKey:", ':', mapOf("env", "test", "aKey", "aVal bKey:bVal cKey:")), + arguments("env:keyWithA:Semicolon bKey:bVal cKey", ':', mapOf("env", "keyWithA:Semicolon", "bKey", "bVal", "cKey", "")), + arguments("env:keyWith: , , Lots:Of:Semicolons ", ':', mapOf("env", "keyWith:", "Lots", "Of:Semicolons")), + arguments("a:b,c,d", ':', mapOf("a", "b", "c", "", "d", "")), + arguments("a,1", ':', mapOf("a", "", "1", "")), + arguments("a:b:c:d", ':', mapOf("a", "b:c:d")), + // edge cases + arguments("noDelimiters", ':', mapOf("noDelimiters", "")), + arguments(" ", ':', Collections.emptyMap()), + arguments(",,,,,,,,,,,,", ':', Collections.emptyMap()), + arguments(", , , , , , ", ':', Collections.emptyMap()) + // spotless:on + ); + } + + @ParameterizedTest + @MethodSource("parseMapWithOptionalMappingsArguments") + void testParseMapWithOptionalMappings( + String mapString, Map expected, boolean lowercaseKeys, String defaultPrefix) { + assertEquals( + expected, + ConfigConverter.parseMapWithOptionalMappings( + mapString, "test", defaultPrefix, lowercaseKeys)); + } + + static Stream + parseMapWithOptionalMappingsArguments() { + return Stream.of( + arguments("header1:one,header2:two", mapOf("header1", "one", "header2", "two"), false, ""), + arguments("header1:one, header2:two", mapOf("header1", "one", "header2", "two"), false, ""), + arguments("header1,header2:two", mapOf("header1", "header1", "header2", "two"), false, ""), + arguments("Header1:one,header2:two", mapOf("header1", "one", "header2", "two"), true, ""), + arguments( + "\"header1:one,header2:two\"", mapOf("\"header1", "one", "header2", "two\""), true, ""), + arguments("header1", mapOf("header1", "header1"), true, ""), + arguments(",header1:tag", mapOf("header1", "tag"), true, ""), + arguments("header1:tag,", mapOf("header1", "tag"), true, ""), + arguments("header:tag:value", mapOf("header", "tag:value"), true, ""), + arguments("", Collections.emptyMap(), true, ""), + arguments((String) null, Collections.emptyMap(), true, ""), + // Test for wildcard header tags + arguments("*", mapOf("*", "datadog.response.headers."), true, "datadog.response.headers"), + arguments("*:", Collections.emptyMap(), true, "datadog.response.headers"), + arguments( + "*,header1:tag", + mapOf("*", "datadog.response.headers."), + true, + "datadog.response.headers"), + arguments( + "header1:tag,*", + mapOf("*", "datadog.response.headers."), + true, + "datadog.response.headers"), + // logs warning: Illegal key only tag starting with non letter '1header' + arguments("1header,header2:two", Collections.emptyMap(), true, ""), + // logs warning: Illegal tag starting with non letter for key 'header' + arguments("header::tag", Collections.emptyMap(), true, ""), + // logs warning: Illegal empty key at position 0 + arguments(":tag", Collections.emptyMap(), true, ""), + // logs warning: Illegal empty key at position 11 + arguments("header:tag,:tag", Collections.emptyMap(), true, "")); + } + + private static Map mapOf(String... keysAndValues) { + Map map = new LinkedHashMap<>(); + for (int i = 0; i < keysAndValues.length; i += 2) { + map.put(keysAndValues[i], keysAndValues[i + 1]); + } + return map; + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.java new file mode 100644 index 00000000000..3a2aca5b006 --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/PropertiesConfigSourceTest.java @@ -0,0 +1,41 @@ +package datadog.trace.bootstrap.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import datadog.trace.test.util.DDJavaSpecification; +import java.util.Properties; +import org.junit.jupiter.api.Test; + +public class PropertiesConfigSourceTest extends DDJavaSpecification { + + @Test + void testNull() { + assertThrows(AssertionError.class, () -> new PropertiesConfigSource(null, true)); + } + + @Test + void configPulledFromProperties() { + Properties props = new Properties(); + props.put("abc", "def"); + props.put("dd.abc", "xyz"); + PropertiesConfigSource source = new PropertiesConfigSource(props, false); + + assertEquals("def", source.get("abc")); + assertEquals("xyz", source.get("dd.abc")); + assertNull(source.get("missing")); + } + + @Test + void configPulledFromPropertiesWithPrefix() { + Properties props = new Properties(); + props.put("abc", "def"); + props.put("dd.abc", "xyz"); + PropertiesConfigSource source = new PropertiesConfigSource(props, true); + + assertEquals("xyz", source.get("abc")); + assertNull(source.get("dd.abc")); + assertNull(source.get("missing")); + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java new file mode 100644 index 00000000000..0f71493082c --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java @@ -0,0 +1,327 @@ +package datadog.trace.bootstrap.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import datadog.trace.api.ConfigCollector; +import datadog.trace.api.ConfigOrigin; +import datadog.trace.api.ConfigSetting; +import datadog.trace.test.util.DDJavaSpecification; +import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.LoggerFactory; +import org.snakeyaml.engine.v2.api.Dump; +import org.snakeyaml.engine.v2.api.DumpSettings; + +@SuppressForbidden +public class StableConfigSourceTest extends DDJavaSpecification { + + @Test + void testFileDoesntExist() { + StableConfigSource config = + new StableConfigSource( + StableConfigSource.LOCAL_STABLE_CONFIG_PATH, ConfigOrigin.LOCAL_STABLE_CONFIG); + + assertEquals(0, config.getKeys().size()); + assertNull(config.getConfigId()); + } + + @Test + void testEmptyFile() throws IOException { + Path filePath = Files.createTempFile("testFile_", ".yaml"); + try { + StableConfigSource config = + new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG); + assertEquals(0, config.getKeys().size()); + assertNull(config.getConfigId()); + } finally { + Files.delete(filePath); + } + } + + @ParameterizedTest + @MethodSource("testFileInvalidFormatArguments") + void testFileInvalidFormat(String configId, String data) throws IOException { + Path filePath = Files.createTempFile("testFile_", ".yaml"); + assertNotNull(filePath, "Failed to create: " + filePath); + try { + writeFileRaw(filePath, configId, data); + StableConfigSource stableCfg = + new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG); + + assertNull(stableCfg.getConfigId()); + assertEquals(0, stableCfg.getKeys().size()); + } finally { + Files.delete(filePath); + } + } + + static Stream testFileInvalidFormatArguments() { + return Stream.of( + arguments((String) null, CORRUPT_YAML), arguments("12345", "this is not yaml format!")); + } + + @Test + void testNullValuesInYaml() throws IOException { + Path filePath = Files.createTempFile("testFile_", ".yaml"); + assertNotNull(filePath, "Failed to create: " + filePath); + try { + // Test the scenario where YAML contains null values for apm_configuration_default and + // apm_configuration_rules + String yaml = "config_id: \"12345\"\napm_configuration_default:\napm_configuration_rules:\n"; + Files.write(filePath, yaml.getBytes()); + + StableConfigSource stableCfg = + new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG); + + // Should not throw NullPointerException and should handle null values gracefully + assertEquals("12345", stableCfg.getConfigId()); + assertEquals(0, stableCfg.getKeys().size()); + } finally { + Files.delete(filePath); + } + } + + @ParameterizedTest + @MethodSource("testFileValidFormatArguments") + void testFileValidFormat(String configId, Map defaultConfigs) throws IOException { + Path filePath = Files.createTempFile("testFile_", ".yaml"); + try { + writeFileYaml(filePath, configId, defaultConfigs); + // verify parsing succeeds without throwing an exception + assertNotNull(new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG)); + } finally { + Files.delete(filePath); + } + } + + static Stream testFileValidFormatArguments() { + Map defaultConfigs = new LinkedHashMap<>(); + defaultConfigs.put("DD_KEY_ONE", "one"); + defaultConfigs.put("DD_KEY_TWO", "two"); + return Stream.of( + arguments("", new HashMap()), arguments("12345", defaultConfigs)); + } + + @ParameterizedTest + @MethodSource("testParseInvalidLogsMappingErrorsArguments") + void testParseInvalidLogsMappingErrors(String yaml, String expectedLogSubstring) + throws IOException { + Logger logbackLogger = (Logger) LoggerFactory.getLogger(StableConfigSource.class); + ListAppender listAppender = new ListAppender<>(); + listAppender.start(); + logbackLogger.addAppender(listAppender); + + java.io.File tempFile = java.io.File.createTempFile("testFile_", ".yaml"); + try { + Files.write(tempFile.toPath(), yaml.getBytes()); + + StableConfigSource stableCfg = + new StableConfigSource(tempFile.getAbsolutePath(), ConfigOrigin.LOCAL_STABLE_CONFIG); + + assertNull(stableCfg.getConfigId()); + assertEquals(0, stableCfg.getKeys().size()); + boolean hasExpectedLog = + listAppender.list.stream() + .anyMatch( + event -> + "WARN".equals(event.getLevel().toString()) + && event.getFormattedMessage().contains(expectedLogSubstring)); + assertTrue(hasExpectedLog, "Expected WARN log containing: " + expectedLogSubstring); + } finally { + tempFile.delete(); + logbackLogger.detachAppender(listAppender); + } + } + + static Stream + testParseInvalidLogsMappingErrorsArguments() { + return Stream.of( + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - key: \"someKey\"\n" + + " matches: [\"someValue\"]\n" + + " operator: equals\n" + + " configuration:\n" + + " DD_SERVICE: \"test\"\n", + "Missing 'origin' in selector"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - origin: process_arguments\n" + + " key: \"-Dfoo\"\n" + + " matches: [\"bar\"]\n" + + " operator: equals\n", + "Missing 'configuration' in rule"), + arguments( + "apm_configuration_rules:\n" + + " - configuration:\n" + + " DD_SERVICE: \"test\"\n", + "Missing 'selectors' in rule"), + arguments( + "apm_configuration_rules:\n" + + " - selectors: \"not-a-list\"\n" + + " configuration:\n" + + " DD_SERVICE: \"test\"\n", + "'selectors' must be a list, but got: String"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - \"not-a-map\"\n" + + " configuration:\n" + + " DD_SERVICE: \"test\"\n", + "Each selector must be a map, but got: String"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - origin: process_arguments\n" + + " key: \"-Dfoo\"\n" + + " matches: [\"bar\"]\n" + + " operator: equals\n" + + " configuration: \"not-a-map\"\n", + "'configuration' must be a map, but got: String"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - origin: process_arguments\n" + + " key: \"-Dfoo\"\n" + + " matches: [\"bar\"]\n" + + " operator: equals\n" + + " configuration: 12345\n", + "'configuration' must be a map, but got: Integer"), + arguments( + "apm_configuration_rules:\n" + " - \"not-a-map\"\n", + "Rule must be a map, but got: String"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - origin: process_arguments\n" + + " key: \"-Dfoo\"\n" + + " matches: \"not-a-list\"\n" + + " operator: equals\n" + + " configuration:\n" + + " DD_SERVICE: \"test\"\n", + "'matches' must be a list, but got: String"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - origin: process_arguments\n" + + " key: \"-Dfoo\"\n" + + " matches: [\"bar\"]\n" + + " configuration:\n" + + " DD_SERVICE: \"test\"\n", + "Missing 'operator' in selector"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " - origin: process_arguments\n" + + " key: \"-Dfoo\"\n" + + " matches: [\"bar\"]\n" + + " operator: 12345\n" + + " configuration:\n" + + " DD_SERVICE: \"test\"\n", + "'operator' must be a string, but got: Integer"), + arguments( + "apm_configuration_rules:\n" + + " - selectors:\n" + + " # origin is missing entirely, should trigger NullPointerException\n" + + " - key: \"-Dfoo\"\n" + + " matches: [\"bar\"]\n" + + " operator: equals\n", + "YAML mapping error in stable configuration file")); + } + + @Test + @SuppressForbidden + void testConfigIdExistsInConfigCollectorWhenUsingStableConfigSource() throws Exception { + Path filePath = Files.createTempFile("testFile_", ".yaml"); + String expectedConfigId = "123"; + + // Create YAML content with config_id and some configuration + String yamlContent = + "config_id: " + + expectedConfigId + + "\napm_configuration_default:\n DD_SERVICE: test-service\n DD_ENV: test-env\n"; + Files.write(filePath, yamlContent.getBytes()); + + // Clear any existing collected config + ConfigCollector.get().collect(); + + try { + StableConfigSource stableConfigSource = + new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG); + + // Create ConfigProvider via reflection (constructor is private) + Constructor constructor = + ConfigProvider.class.getDeclaredConstructor(ConfigProvider.Source[].class); + constructor.setAccessible(true); + ConfigProvider configProvider = + constructor.newInstance((Object) new ConfigProvider.Source[] {stableConfigSource}); + + // Trigger config collection by getting a value + configProvider.getString("SERVICE", "default-service"); + + Map> collectedConfigs = + ConfigCollector.get().collect(); + Map localStableConfigs = + collectedConfigs.get(ConfigOrigin.LOCAL_STABLE_CONFIG); + assertNotNull(localStableConfigs, "No configs collected for LOCAL_STABLE_CONFIG origin"); + ConfigSetting serviceSetting = localStableConfigs.get("SERVICE"); + assertNotNull(serviceSetting, "No SERVICE setting collected"); + assertEquals(expectedConfigId, serviceSetting.configId); + assertEquals("test-service", serviceSetting.value); + assertEquals(ConfigOrigin.LOCAL_STABLE_CONFIG, serviceSetting.origin); + } finally { + Files.delete(filePath); + } + } + + // Corrupt YAML string variable used for testing + private static final String CORRUPT_YAML = + " \n" + " abc: 123\n" + " def:\n" + " ghi: \"jkl\"\n" + " lmn: 456\n"; + + private static void writeFileYaml( + Path filePath, String configId, Map defaultConfigs) throws IOException { + Map yamlData = new HashMap<>(); + + if (configId != null && !configId.isEmpty()) { + yamlData.put("config_id", configId); + } + + if (defaultConfigs != null && !defaultConfigs.isEmpty()) { + yamlData.put("apm_configuration_default", defaultConfigs); + } + + DumpSettings settings = DumpSettings.builder().build(); + Dump dump = new Dump(settings); + String yamlContent = dump.dumpToString(yamlData); + + try (FileWriter writer = new FileWriter(filePath.toFile())) { + writer.write(yamlContent); + } + } + + private static void writeFileRaw(Path filePath, String configId, String data) throws IOException { + String content = configId + "\n" + data; + Files.write(filePath, content.getBytes(), StandardOpenOption.WRITE); + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java new file mode 100644 index 00000000000..6fce6c80ea1 --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java @@ -0,0 +1,44 @@ +package datadog.trace.bootstrap.config.provider.stableconfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class StableConfigMappingExceptionTest { + + @Test + void constructorsWorkAsExpected() { + StableConfigMappingException ex1 = new StableConfigMappingException("msg"); + StableConfigMappingException ex2 = new StableConfigMappingException("msg2"); + + assertEquals("msg", ex1.getMessage()); + assertNull(ex1.getCause()); + assertEquals("msg2", ex2.getMessage()); + } + + @Test + void safeToStringHandlesNull() { + assertEquals("null", StableConfigMappingException.safeToString(null)); + } + + @Test + void safeToStringHandlesShortString() { + assertEquals("short string", StableConfigMappingException.safeToString("short string")); + } + + @Test + void safeToStringHandlesLongString() { + char[] chars = new char[101]; + Arrays.fill(chars, 'a'); + String longStr = new String(chars); + + char[] halfChars = new char[50]; + Arrays.fill(halfChars, 'a'); + String half = new String(halfChars); + + assertEquals( + half + "...(truncated)..." + half, StableConfigMappingException.safeToString(longStr)); + } +} From 11e019d05c46d4bfbc465081638925974a70aff8 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 4 Jun 2026 14:20:13 -0400 Subject: [PATCH 2/6] Use TableTest --- .../datadog/trace/api/ConfigSettingTest.java | 45 ++- .../config/provider/ConfigConverterTest.java | 311 +++++++----------- .../provider/StableConfigSourceTest.java | 37 +-- .../utils/tabletest/BoxedValueConverter.java | 51 +++ 4 files changed, 207 insertions(+), 237 deletions(-) create mode 100644 utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/BoxedValueConverter.java diff --git a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java index 09d874eff5b..3b361435c3d 100644 --- a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java +++ b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java @@ -4,19 +4,26 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.params.provider.Arguments.arguments; +import datadog.trace.junit.utils.tabletest.BoxedValueConverter; import java.util.Arrays; import java.util.BitSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.provider.MethodSource; import org.tabletest.junit.TableTest; public class ConfigSettingTest { - @ParameterizedTest - @MethodSource("supportsEqualityCheckArguments") + @TableTest({ + "scenario | key1 | key2 | value1 | value2 | origin1 | origin2 | expectedEqual", + "equal | key | key | value | value | DEFAULT | DEFAULT | true ", + "different key | key | key2 | value | value | ENV | ENV | false ", + "different value | key | key | value2 | value | JVM_PROP | JVM_PROP | false ", + "different origin | key | key | value | value | ENV | DEFAULT | false " + }) void supportsEqualityCheck( String key1, String key2, @@ -43,15 +50,6 @@ void supportsEqualityCheck( } } - static Stream supportsEqualityCheckArguments() { - return Stream.of( - arguments("key", "key", "value", "value", ConfigOrigin.DEFAULT, ConfigOrigin.DEFAULT, true), - arguments("key", "key2", "value", "value", ConfigOrigin.ENV, ConfigOrigin.ENV, false), - arguments( - "key", "key", "value2", "value", ConfigOrigin.JVM_PROP, ConfigOrigin.JVM_PROP, false), - arguments("key", "key", "value", "value", ConfigOrigin.ENV, ConfigOrigin.DEFAULT, false)); - } - @TableTest({ "scenario | key | value | filteredValue", "DD_API_KEY | DD_API_KEY | somevalue | ", @@ -64,23 +62,20 @@ void filtersKeyValues(String key, String value, String filteredValue) { assertEquals(filteredValue, ConfigSetting.of(key, value, ConfigOrigin.DEFAULT).stringValue()); } - @ParameterizedTest - @MethodSource("supportBasicTypesArguments") - void supportBasicTypes(Object value, String rendered) { + @TableTest({ + "scenario | value | rendered", + "null | | ", + "boolean | true | true ", + "boolean | false | false ", + "integer | 1 | 1 ", + "double | 1.0 | 1.0 ", + "float | 2.33f | 2.33 ", + "string | string | string " + }) + void supportBasicTypes(@ConvertWith(BoxedValueConverter.class) Object value, String rendered) { assertEquals(rendered, ConfigSetting.of("key", value, ConfigOrigin.DEFAULT).stringValue()); } - static Stream supportBasicTypesArguments() { - return Stream.of( - arguments((Object) null, (String) null), - arguments(true, "true"), - arguments(false, "false"), - arguments(1, "1"), - arguments(1.0d, "1.0"), - arguments(2.33f, "2.33"), - arguments("string", "string")); - } - @ParameterizedTest @MethodSource("convertIterableMapAndBitSetArguments") void convertIterableMapAndBitSetToString(Object value, String rendered) { diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java index 82548f7498a..2a7cfbb5467 100644 --- a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/ConfigConverterTest.java @@ -2,231 +2,166 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.jupiter.api.Assertions.assertTrue; import datadog.trace.test.util.DDJavaSpecification; import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; +import org.tabletest.junit.TableTest; public class ConfigConverterTest extends DDJavaSpecification { - @ParameterizedTest - @MethodSource("convertBooleanPropertiesArguments") + @TableTest({ + "scenario | stringValue | expectedConvertedValue", + "true | true | true ", + "TRUE | TRUE | true ", + "True | True | true ", + "1 | 1 | true ", + "false | false | false ", + "null | | ", + "empty string | '' | ", + "0 | 0 | false " + }) void convertBooleanProperties(String stringValue, Boolean expectedConvertedValue) { assertEquals(expectedConvertedValue, ConfigConverter.valueOf(stringValue, Boolean.class)); } - static Stream convertBooleanPropertiesArguments() { - return Stream.of( - arguments("true", true), - arguments("TRUE", true), - arguments("True", true), - arguments("1", true), - arguments("false", false), - arguments((String) null, (Boolean) null), - arguments("", (Boolean) null), - arguments("0", false)); - } - - @ParameterizedTest - @ValueSource( - strings = { - "42.42", - "tru", - "truee", - "true ", - " true", - " true ", - " true ", - "notABool", - "yes", - "no", - "on", - "off" - }) + @TableTest({ + "invalidValue", + "42.42 ", + "tru ", + "truee ", + "'true ' ", + "' true' ", + "' true ' ", + "' true ' ", + "notABool ", + "yes ", + "no ", + "on ", + "off " + }) void convertBooleanPropertiesThrowsExceptionForInvalidValues(String invalidValue) { ConfigConverter.InvalidBooleanValueException exception = assertThrows( ConfigConverter.InvalidBooleanValueException.class, () -> ConfigConverter.valueOf(invalidValue, Boolean.class)); - assertEquals(true, exception.getMessage().contains("Invalid boolean value:")); + assertTrue(exception.getMessage().contains("Invalid boolean value:")); } - @ParameterizedTest - @MethodSource("parseMapProperlyArguments") + // spotless:off + @TableTest({ + "mapString | expected ", + "a:1, a:2, a:3 | [a: '3'] ", + "a:b,c:d,e: | [a: b, c: d] ", + "a:1 a:2 a:3 | [a: '3'] ", + "a:b c:d e: | [a: b, c: d] ", + "a:a; | [a: a;] ", + "a:1, a:2, a:3 | [a: '3'] ", + "a:1 a:2 a:3 | [a: '3'] ", + "a:b,c:d,e: | [a: b, c: d] ", + "a:b c:d e: | [a: b, c: d] ", + "'key 1!:va|ue_1,' | [key 1!: 'va|ue_1'] ", + "'key 1!:va|ue_1 ' | [key 1!: 'va|ue_1'] ", + "' key1 :value1 ,\t key2: value2' | [key1: value1, key2: value2] ", + "a:b, b:c, c:d, d: e | [a: b, b: c, c: d, d: e] ", + "key1 :value1 \t key2: value2 | [key1: value1, key2: value2] ", + "dyno:web.1 dynotype:web appname:****** | [dyno: web.1, dynotype: web, appname: ******] ", + "is:val:id | [is: 'val:id'] ", + "a:b,is:val:id,x:y | [a: b, is: 'val:id', x: y] ", + "a:b:c:d | [a: 'b:c:d'] ", + "fooa:barb, foob:barc, fooc: bard, food: bare, | [fooa: barb, foob: barc, fooc: bard, food: bare] ", + "a:b=c=d | [a: b=c=d] ", + "a: | [:] ", + "a:b,c,d | [:] ", + "a:b,c,d,k:v | [:] ", + "'' | [:] ", + "1 | [:] ", + "a | [:] ", + "a,1 | [:] ", + "!a | [:] ", + "' ' | [:] ", + ",,,, | [:] ", + ":,:,:,: | [:] ", + "': : : : ' | [:] ", + ":::: | [:] ", + "key1:val1 with_space:and_colon, key2:val2 | [:] " + }) void parseMapProperly(String mapString, Map expected) { - // spotless:off assertEquals(expected, ConfigConverter.parseMap(mapString, "test")); - // spotless:on - } - - static Stream parseMapProperlyArguments() { - return Stream.of( - // spotless:off - arguments("a:1, a:2, a:3", mapOf("a", "3")), - arguments("a:b,c:d,e:", mapOf("a", "b", "c", "d")), - // space separated - arguments("a:1 a:2 a:3", mapOf("a", "3")), - arguments("a:b c:d e:", mapOf("a", "b", "c", "d")), - // More different string variants: - arguments("a:a;", mapOf("a", "a;")), - arguments("a:1, a:2, a:3", mapOf("a", "3")), - arguments("a:1 a:2 a:3", mapOf("a", "3")), - arguments("a:b,c:d,e:", mapOf("a", "b", "c", "d")), - arguments("a:b c:d e:", mapOf("a", "b", "c", "d")), - arguments("key 1!:va|ue_1,", mapOf("key 1!", "va|ue_1")), - arguments("key 1!:va|ue_1 ", mapOf("key 1!", "va|ue_1")), - arguments(" key1 :value1 ,\t key2: value2", mapOf("key1", "value1", "key2", "value2")), - arguments("a:b, b:c, c:d, d: e", mapOf("a", "b", "b", "c", "c", "d", "d", "e")), - arguments("key1 :value1 \t key2: value2", mapOf("key1", "value1", "key2", "value2")), - arguments("dyno:web.1 dynotype:web appname:******", mapOf("dyno", "web.1", "dynotype", "web", "appname", "******")), - arguments("is:val:id", mapOf("is", "val:id")), - arguments("a:b,is:val:id,x:y", mapOf("a", "b", "is", "val:id", "x", "y")), - arguments("a:b:c:d", mapOf("a", "b:c:d")), - arguments("fooa:barb, foob:barc, fooc: bard, food: bare,", mapOf("fooa", "barb", "foob", "barc", "fooc", "bard", "food", "bare")), - arguments("a:b=c=d", mapOf("a", "b=c=d")), - // Illegal - arguments("a:", Collections.emptyMap()), - arguments("a:b,c,d", Collections.emptyMap()), - arguments("a:b,c,d,k:v", Collections.emptyMap()), - arguments("", Collections.emptyMap()), - arguments("1", Collections.emptyMap()), - arguments("a", Collections.emptyMap()), - arguments("a,1", Collections.emptyMap()), - arguments("!a", Collections.emptyMap()), - arguments(" ", Collections.emptyMap()), - arguments(",,,,", Collections.emptyMap()), - arguments(":,:,:,:", Collections.emptyMap()), - arguments(": : : : ", Collections.emptyMap()), - arguments("::::", Collections.emptyMap()), - arguments("key1:val1 with_space:and_colon, key2:val2", Collections.emptyMap()) - // spotless:on - ); } - @ParameterizedTest - @MethodSource("parseMapWithSeparatorArguments") + @TableTest({ + "mapString | separator | expected ", + "a=1, a=2, a=3 | = | [a: '3'] ", + "a=b,c=d,e= | = | [a: b, c: d] ", + "a;b,c;d,e; | ; | [a: b, c: d] ", + "a=1 a=2 a=3 | = | [a: '3'] ", + "a=b c=d e= | = | [a: b, c: d] ", + "a=b=c=d | = | [a: b=c=d] ", + "fooa=barb, foob=barc, fooc= bard, food= bare, | = | [fooa: barb, foob: barc, fooc: bard, food: bare] ", + "a=b:c:d | = | [a: 'b:c:d'] ", + "a= | = | [:] ", + "==== | = | [:] " + }) void parseMapWithSeparator(String mapString, char separator, Map expected) { - // spotless:off assertEquals(expected, ConfigConverter.parseMap(mapString, "test", separator)); - // spotless:on - } - - static Stream parseMapWithSeparatorArguments() { - return Stream.of( - // spotless:off - arguments("a=1, a=2, a=3", '=', mapOf("a", "3")), - arguments("a=b,c=d,e=", '=', mapOf("a", "b", "c", "d")), - arguments("a;b,c;d,e;", ';', mapOf("a", "b", "c", "d")), - // space separated - arguments("a=1 a=2 a=3", '=', mapOf("a", "3")), - arguments("a=b c=d e=", '=', mapOf("a", "b", "c", "d")), - // More different string variants - arguments("a=b=c=d", '=', mapOf("a", "b=c=d")), - arguments("fooa=barb, foob=barc, fooc= bard, food= bare,", '=', mapOf("fooa", "barb", "foob", "barc", "fooc", "bard", "food", "bare")), - arguments("a=b:c:d", '=', mapOf("a", "b:c:d")), - // Illegal - arguments("a=", '=', Collections.emptyMap()), - arguments("====", '=', Collections.emptyMap()) - // spotless:on - ); } - @ParameterizedTest - @MethodSource("parseTraceTagsMapArguments") + @TableTest({ + "mapString | separator | expected ", + "key1:value1,key2:value2 | : | [key1: value1, key2: value2] ", + "key1:value1 key2:value2 | : | [key1: value1, key2: value2] ", + "env:test aKey:aVal bKey:bVal cKey: | : | [env: test, aKey: aVal, bKey: bVal, cKey: ''] ", + "env:test,aKey:aVal,bKey:bVal,cKey: | : | [env: test, aKey: aVal, bKey: bVal, cKey: ''] ", + "env:test,aKey:aVal bKey:bVal cKey: | : | [env: test, aKey: 'aVal bKey:bVal cKey:'] ", + "env:test bKey :bVal dKey: dVal cKey: | : | [env: test, bKey: '', dKey: '', dVal: '', cKey: '']", + "env :test, aKey : aVal bKey:bVal cKey: | : | [env: test, aKey: 'aVal bKey:bVal cKey:'] ", + "env:keyWithA:Semicolon bKey:bVal cKey | : | [env: 'keyWithA:Semicolon', bKey: bVal, cKey: ''] ", + "env:keyWith: , , Lots:Of:Semicolons | : | [env: 'keyWith:', Lots: 'Of:Semicolons'] ", + "a:b,c,d | : | [a: b, c: '', d: ''] ", + "a,1 | : | [a: '', 1: ''] ", + "a:b:c:d | : | [a: 'b:c:d'] ", + "noDelimiters | : | [noDelimiters: ''] ", + "' ' | : | [:] ", + ",,,,,,,,,,,, | : | [:] ", + "', , , , , , ' | : | [:] " + }) void parsingMapWithListOfArgSeparatorsForWithKeyValueSeparator( String mapString, char separator, Map expected) { // testing parsing for DD_TAGS List separatorList = Arrays.asList(',', ' '); - // spotless:off assertEquals(expected, ConfigConverter.parseTraceTagsMap(mapString, separator, separatorList)); - // spotless:on } - static Stream parseTraceTagsMapArguments() { - return Stream.of( - // spotless:off - arguments("key1:value1,key2:value2", ':', mapOf("key1", "value1", "key2", "value2")), - arguments("key1:value1 key2:value2", ':', mapOf("key1", "value1", "key2", "value2")), - arguments("env:test aKey:aVal bKey:bVal cKey:", ':', mapOf("env", "test", "aKey", "aVal", "bKey", "bVal", "cKey", "")), - arguments("env:test,aKey:aVal,bKey:bVal,cKey:", ':', mapOf("env", "test", "aKey", "aVal", "bKey", "bVal", "cKey", "")), - arguments("env:test,aKey:aVal bKey:bVal cKey:", ':', mapOf("env", "test", "aKey", "aVal bKey:bVal cKey:")), - arguments("env:test bKey :bVal dKey: dVal cKey:", ':', mapOf("env", "test", "bKey", "", "dKey", "", "dVal", "", "cKey", "")), - arguments("env :test, aKey : aVal bKey:bVal cKey:", ':', mapOf("env", "test", "aKey", "aVal bKey:bVal cKey:")), - arguments("env:keyWithA:Semicolon bKey:bVal cKey", ':', mapOf("env", "keyWithA:Semicolon", "bKey", "bVal", "cKey", "")), - arguments("env:keyWith: , , Lots:Of:Semicolons ", ':', mapOf("env", "keyWith:", "Lots", "Of:Semicolons")), - arguments("a:b,c,d", ':', mapOf("a", "b", "c", "", "d", "")), - arguments("a,1", ':', mapOf("a", "", "1", "")), - arguments("a:b:c:d", ':', mapOf("a", "b:c:d")), - // edge cases - arguments("noDelimiters", ':', mapOf("noDelimiters", "")), - arguments(" ", ':', Collections.emptyMap()), - arguments(",,,,,,,,,,,,", ':', Collections.emptyMap()), - arguments(", , , , , , ", ':', Collections.emptyMap()) - // spotless:on - ); - } - - @ParameterizedTest - @MethodSource("parseMapWithOptionalMappingsArguments") + @TableTest({ + "mapString | expected | lowercaseKeys | defaultPrefix ", + "header1:one,header2:two | [header1: one, header2: two] | false | '' ", + "header1:one, header2:two | [header1: one, header2: two] | false | '' ", + "header1,header2:two | [header1: header1, header2: two] | false | '' ", + "Header1:one,header2:two | [header1: one, header2: two] | true | '' ", + "'\"header1:one,header2:two\"' | ['\"header1': one, header2: 'two\"'] | true | '' ", + "header1 | [header1: header1] | true | '' ", + ",header1:tag | [header1: tag] | true | '' ", + "header1:tag, | [header1: tag] | true | '' ", + "header:tag:value | [header: 'tag:value'] | true | '' ", + "'' | [:] | true | '' ", + " | [:] | true | '' ", + "* | [*: 'datadog.response.headers.'] | true | datadog.response.headers ", + "*: | [:] | true | datadog.response.headers ", + "*,header1:tag | [*: 'datadog.response.headers.'] | true | datadog.response.headers ", + "header1:tag,* | [*: 'datadog.response.headers.'] | true | datadog.response.headers ", + "1header,header2:two | [:] | true | '' ", + "header::tag | [:] | true | '' ", + ":tag | [:] | true | '' ", + "header:tag,:tag | [:] | true | '' " + }) void testParseMapWithOptionalMappings( String mapString, Map expected, boolean lowercaseKeys, String defaultPrefix) { assertEquals( expected, - ConfigConverter.parseMapWithOptionalMappings( - mapString, "test", defaultPrefix, lowercaseKeys)); - } - - static Stream - parseMapWithOptionalMappingsArguments() { - return Stream.of( - arguments("header1:one,header2:two", mapOf("header1", "one", "header2", "two"), false, ""), - arguments("header1:one, header2:two", mapOf("header1", "one", "header2", "two"), false, ""), - arguments("header1,header2:two", mapOf("header1", "header1", "header2", "two"), false, ""), - arguments("Header1:one,header2:two", mapOf("header1", "one", "header2", "two"), true, ""), - arguments( - "\"header1:one,header2:two\"", mapOf("\"header1", "one", "header2", "two\""), true, ""), - arguments("header1", mapOf("header1", "header1"), true, ""), - arguments(",header1:tag", mapOf("header1", "tag"), true, ""), - arguments("header1:tag,", mapOf("header1", "tag"), true, ""), - arguments("header:tag:value", mapOf("header", "tag:value"), true, ""), - arguments("", Collections.emptyMap(), true, ""), - arguments((String) null, Collections.emptyMap(), true, ""), - // Test for wildcard header tags - arguments("*", mapOf("*", "datadog.response.headers."), true, "datadog.response.headers"), - arguments("*:", Collections.emptyMap(), true, "datadog.response.headers"), - arguments( - "*,header1:tag", - mapOf("*", "datadog.response.headers."), - true, - "datadog.response.headers"), - arguments( - "header1:tag,*", - mapOf("*", "datadog.response.headers."), - true, - "datadog.response.headers"), - // logs warning: Illegal key only tag starting with non letter '1header' - arguments("1header,header2:two", Collections.emptyMap(), true, ""), - // logs warning: Illegal tag starting with non letter for key 'header' - arguments("header::tag", Collections.emptyMap(), true, ""), - // logs warning: Illegal empty key at position 0 - arguments(":tag", Collections.emptyMap(), true, ""), - // logs warning: Illegal empty key at position 11 - arguments("header:tag,:tag", Collections.emptyMap(), true, "")); - } - - private static Map mapOf(String... keysAndValues) { - Map map = new LinkedHashMap<>(); - for (int i = 0; i < keysAndValues.length; i += 2) { - map.put(keysAndValues[i], keysAndValues[i + 1]); - } - return map; + ConfigConverter.parseMapWithOptionalMappings(mapString, "test", defaultPrefix, lowercaseKeys)); } + // spotless:on } diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java index 0f71493082c..38062a49be0 100644 --- a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.java @@ -21,7 +21,6 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -30,6 +29,7 @@ import org.slf4j.LoggerFactory; import org.snakeyaml.engine.v2.api.Dump; import org.snakeyaml.engine.v2.api.DumpSettings; +import org.tabletest.junit.TableTest; @SuppressForbidden public class StableConfigSourceTest extends DDJavaSpecification { @@ -57,8 +57,11 @@ void testEmptyFile() throws IOException { } } - @ParameterizedTest - @MethodSource("testFileInvalidFormatArguments") + @TableTest({ + "scenario | configId | data ", + "corrupt yaml | | not_valid_stable_config ", + "invalid format | 12345 | this is not yaml format!" + }) void testFileInvalidFormat(String configId, String data) throws IOException { Path filePath = Files.createTempFile("testFile_", ".yaml"); assertNotNull(filePath, "Failed to create: " + filePath); @@ -74,11 +77,6 @@ void testFileInvalidFormat(String configId, String data) throws IOException { } } - static Stream testFileInvalidFormatArguments() { - return Stream.of( - arguments((String) null, CORRUPT_YAML), arguments("12345", "this is not yaml format!")); - } - @Test void testNullValuesInYaml() throws IOException { Path filePath = Files.createTempFile("testFile_", ".yaml"); @@ -100,9 +98,12 @@ void testNullValuesInYaml() throws IOException { } } - @ParameterizedTest - @MethodSource("testFileValidFormatArguments") - void testFileValidFormat(String configId, Map defaultConfigs) throws IOException { + @TableTest({ + "configId | defaultConfigs ", + "'' | [:] ", + "12345 | [DD_KEY_ONE: one, DD_KEY_TWO: two]" + }) + void testFileValidFormat(String configId, Map defaultConfigs) throws IOException { Path filePath = Files.createTempFile("testFile_", ".yaml"); try { writeFileYaml(filePath, configId, defaultConfigs); @@ -113,14 +114,6 @@ void testFileValidFormat(String configId, Map defaultConfigs) th } } - static Stream testFileValidFormatArguments() { - Map defaultConfigs = new LinkedHashMap<>(); - defaultConfigs.put("DD_KEY_ONE", "one"); - defaultConfigs.put("DD_KEY_TWO", "two"); - return Stream.of( - arguments("", new HashMap()), arguments("12345", defaultConfigs)); - } - @ParameterizedTest @MethodSource("testParseInvalidLogsMappingErrorsArguments") void testParseInvalidLogsMappingErrors(String yaml, String expectedLogSubstring) @@ -295,12 +288,8 @@ void testConfigIdExistsInConfigCollectorWhenUsingStableConfigSource() throws Exc } } - // Corrupt YAML string variable used for testing - private static final String CORRUPT_YAML = - " \n" + " abc: 123\n" + " def:\n" + " ghi: \"jkl\"\n" + " lmn: 456\n"; - private static void writeFileYaml( - Path filePath, String configId, Map defaultConfigs) throws IOException { + Path filePath, String configId, Map defaultConfigs) throws IOException { Map yamlData = new HashMap<>(); if (configId != null && !configId.isEmpty()) { diff --git a/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/BoxedValueConverter.java b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/BoxedValueConverter.java new file mode 100644 index 00000000000..185a1f55b7a --- /dev/null +++ b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/BoxedValueConverter.java @@ -0,0 +1,51 @@ +package datadog.trace.junit.utils.tabletest; + +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; + +/** + * Converts a String cell value to the most specific boxed Java primitive type. Use with + * {@code @ConvertWith(BoxedValueConverter.class)} on {@code Object}-typed parameters when the test + * needs actual typed values (e.g. {@code Float} not {@code String "2.33f"}). + * + *

Conversion rules: + * + *

    + *
  • blank/null -> null + *
  • {@code "true"}/{@code "false"} -> {@link Boolean} + *
  • ends with {@code "f"} -> {@link Float} + *
  • contains {@code "."} -> {@link Double} + *
  • parseable as integer -> {@link Integer} + *
  • otherwise -> {@link String} + *
+ */ +public class BoxedValueConverter implements ArgumentConverter { + + @Override + public Object convert(Object source, ParameterContext context) + throws ArgumentConversionException { + if (source == null) return null; + String s = source.toString(); + if (s.isEmpty()) return null; + if ("true".equals(s)) return Boolean.TRUE; + if ("false".equals(s)) return Boolean.FALSE; + if (s.endsWith("f")) { + try { + return Float.parseFloat(s.substring(0, s.length() - 1)); + } catch (NumberFormatException ignored) { + } + } + if (s.contains(".")) { + try { + return Double.parseDouble(s); + } catch (NumberFormatException ignored) { + } + } + try { + return Integer.parseInt(s); + } catch (NumberFormatException ignored) { + } + return s; + } +} From aa40b743018f0573c336f0d6577e6639f2519186 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 4 Jun 2026 14:34:33 -0400 Subject: [PATCH 3/6] Move StableConfigMappingExceptionTest --- .../StableConfigMappingExceptionTest.java | 60 +++++++++++++++++++ .../StableConfigMappingExceptionTest.java | 44 -------------- 2 files changed, 60 insertions(+), 44 deletions(-) create mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.java delete mode 100644 utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.java new file mode 100644 index 00000000000..8b2476197f3 --- /dev/null +++ b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/StableConfigMappingExceptionTest.java @@ -0,0 +1,60 @@ +package datadog.trace.bootstrap.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.bootstrap.config.provider.stableconfig.StableConfigMappingException; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class StableConfigMappingExceptionTest { + + @Test + void constructorsWorkAsExpected() { + StableConfigMappingException ex1 = new StableConfigMappingException("msg"); + StableConfigMappingException ex2 = new StableConfigMappingException("msg2"); + + assertEquals("msg", ex1.getMessage()); + assertNull(ex1.getCause()); + assertEquals("msg2", ex2.getMessage()); + } + + @Test + void safeToStringHandlesNull() { + StableConfigMappingException ex = + assertThrows( + StableConfigMappingException.class, + () -> StableConfigMappingException.throwStableConfigMappingException("msg", null)); + assertTrue(ex.getMessage().endsWith(" null")); + } + + @Test + void safeToStringHandlesShortString() { + StableConfigMappingException ex = + assertThrows( + StableConfigMappingException.class, + () -> + StableConfigMappingException.throwStableConfigMappingException( + "msg", "short string")); + assertTrue(ex.getMessage().endsWith(" short string")); + } + + @Test + void safeToStringHandlesLongString() { + char[] chars = new char[101]; + Arrays.fill(chars, 'a'); + String longStr = new String(chars); + + StableConfigMappingException ex = + assertThrows( + StableConfigMappingException.class, + () -> StableConfigMappingException.throwStableConfigMappingException("msg", longStr)); + + char[] halfChars = new char[50]; + Arrays.fill(halfChars, 'a'); + String half = new String(halfChars); + assertTrue(ex.getMessage().endsWith(" " + half + "...(truncated)..." + half)); + } +} diff --git a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java b/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java deleted file mode 100644 index 6fce6c80ea1..00000000000 --- a/utils/config-utils/src/test/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfigMappingExceptionTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package datadog.trace.bootstrap.config.provider.stableconfig; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.util.Arrays; -import org.junit.jupiter.api.Test; - -public class StableConfigMappingExceptionTest { - - @Test - void constructorsWorkAsExpected() { - StableConfigMappingException ex1 = new StableConfigMappingException("msg"); - StableConfigMappingException ex2 = new StableConfigMappingException("msg2"); - - assertEquals("msg", ex1.getMessage()); - assertNull(ex1.getCause()); - assertEquals("msg2", ex2.getMessage()); - } - - @Test - void safeToStringHandlesNull() { - assertEquals("null", StableConfigMappingException.safeToString(null)); - } - - @Test - void safeToStringHandlesShortString() { - assertEquals("short string", StableConfigMappingException.safeToString("short string")); - } - - @Test - void safeToStringHandlesLongString() { - char[] chars = new char[101]; - Arrays.fill(chars, 'a'); - String longStr = new String(chars); - - char[] halfChars = new char[50]; - Arrays.fill(halfChars, 'a'); - String half = new String(halfChars); - - assertEquals( - half + "...(truncated)..." + half, StableConfigMappingException.safeToString(longStr)); - } -} From c2d35780de2554af38ded0306670fe36afed52eb Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 4 Jun 2026 15:04:04 -0400 Subject: [PATCH 4/6] Convert time-utils and container-utils --- .../common/container/ContainerInfoTest.groovy | 365 ----------------- .../container/ServerlessInfoTest.groovy | 47 --- .../common/container/ContainerInfoTest.java | 377 ++++++++++++++++++ .../common/container/ServerlessInfoTest.java | 72 ++++ .../trace/api/time/TimeUtilsTest.groovy | 50 --- .../datadog/trace/api/time/TimeUtilsTest.java | 51 +++ 6 files changed, 500 insertions(+), 462 deletions(-) delete mode 100644 utils/container-utils/src/test/groovy/datadog/common/container/ContainerInfoTest.groovy delete mode 100644 utils/container-utils/src/test/groovy/datadog/common/container/ServerlessInfoTest.groovy create mode 100644 utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java create mode 100644 utils/container-utils/src/test/java/datadog/common/container/ServerlessInfoTest.java delete mode 100644 utils/time-utils/src/test/groovy/datadog/trace/api/time/TimeUtilsTest.groovy create mode 100644 utils/time-utils/src/test/java/datadog/trace/api/time/TimeUtilsTest.java diff --git a/utils/container-utils/src/test/groovy/datadog/common/container/ContainerInfoTest.groovy b/utils/container-utils/src/test/groovy/datadog/common/container/ContainerInfoTest.groovy deleted file mode 100644 index 1b74763b5b3..00000000000 --- a/utils/container-utils/src/test/groovy/datadog/common/container/ContainerInfoTest.groovy +++ /dev/null @@ -1,365 +0,0 @@ -package datadog.common.container - -import datadog.trace.test.util.DDSpecification - -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.text.ParseException - -class ContainerInfoTest extends DDSpecification { - - def "CGroupInfo is parsed from individual lines"() { - when: - ContainerInfo.CGroupInfo cGroupInfo = ContainerInfo.parseLine(line) - - then: - cGroupInfo.getId() == id - cGroupInfo.getPath() == path - cGroupInfo.getControllers() == controllers - cGroupInfo.getContainerId() == containerId - cGroupInfo.podId == podId - - // Examples from container tagging rfc and Qard/container-info - where: - // spotless:off - id | controllers | path | containerId | podId | line - - // Docker examples - 13 | ["name=systemd"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 12 | ["pids"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 11 | ["hugetlb"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 10 | ["net_prio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 9 | ["perf_event"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 8 | ["net_cls"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 7 | ["freezer"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 6 | ["devices"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 5 | ["memory"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 4 | ["blkio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 3 | ["cpuacct"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 2 | ["cpu"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - 1 | ["cpuset"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" - - // Kubernates examples - 11 | ["perf_event"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 10 | ["pids"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 9 | ["memory"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 8 | ["cpu", "cpuacct"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 7 | ["blkio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 6 | ["cpuset"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 5 | ["devices"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 4 | ["freezer"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 3 | ["net_cls", "net_prio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 2 | ["hugetlb"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 1 | ["name=systemd"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - - //ECS examples - 9 | ["perf_event"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 8 | ["memory"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 7 | ["hugetlb"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 6 | ["freezer"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 5 | ["devices"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 4 | ["cpuset"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 3 | ["cpuacct"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 2 | ["cpu"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - 1 | ["blkio"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" - - //Fargate Examples - 11 | ["hugetlb"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 10 | ["pids"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 9 | ["cpuset"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 8 | ["net_cls", "net_prio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 7 | ["cpu", "cpuacct"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 6 | ["perf_event"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 5 | ["freezer"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 4 | ["devices"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 3 | ["blkio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 2 | ["memory"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 1 | ["name=systemd"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" - 1 | ["name=systemd"] | "/ecs/34dc0b5e626f2c5c4c5170e34b10e765-1234567890" | "34dc0b5e626f2c5c4c5170e34b10e765-1234567890" | null | "1:name=systemd:/ecs/34dc0b5e626f2c5c4c5170e34b10e765-1234567890" - - // PCF example - 1 | ["freezer"] | "/garden/6f265890-5165-7fab-6b52-18d1" | "6f265890-5165-7fab-6b52-18d1" | null | "1:freezer:/garden/6f265890-5165-7fab-6b52-18d1" - - //Reference impl examples - 1 | ["name=systemd"] | "/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope" | "cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411" | null | "1:name=systemd:/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope" - 1 | ["name=systemd"] | "/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex" | null | null | "1:name=systemd:/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex" - 1 | ["name=systemd"] | "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope" | "2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63" | "90d81341_92de_11e7_8cf2_507b9d4141fa" | "1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope" - // spotless:on - } - - def "Container info parsed from file content"() { - when: - ContainerInfo containerInfo = ContainerInfo.parse(content) - - then: - containerInfo.getContainerId() == containerId - containerInfo.getPodId() == podId - containerInfo.getCGroups().size() == size - - where: - // spotless:off - containerId | podId | size | content - // Docker - "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | 13 | """13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 -1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860""" - - // Kubernetes - "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | 11 | """11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 -1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1""" - "7b8952daecf4c0e44bbcefe1b5c5ebc7b4839d4eefeccefe694709d3809b6199" | "2d3da189_6407_48e3_9ab6_78188d75e609" | 1 | "1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod2d3da189_6407_48e3_9ab6_78188d75e609.slice/docker-7b8952daecf4c0e44bbcefe1b5c5ebc7b4839d4eefeccefe694709d3809b6199.scope" - - // ECS - "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | 9 | """9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce -1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce""" - - // Fargate 1.3- - "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | 11 | """11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da""" - - // Fargate 1.4+ - "34dc0b5e626f2c5c4c5170e34b10e765-1234567890" | null | 11 | """11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da -1:name=systemd:/ecs/34dc0b5e626f2c5c4c5170e34b10e765-1234567890""" - - // EKS Fargate cgroup with trailing cgroup v2 membership entry (0::...) - "cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc" | "defa568d-ff14-43d9-9a63-9e39ee9b39b4" | 13 | """12:misc:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -11:cpuset:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -10:perf_event:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -9:blkio:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -8:net_cls,net_prio:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -7:memory:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -6:cpu,cpuacct:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -5:pids:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -4:devices:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -3:hugetlb:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -2:freezer:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -1:name=systemd:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc -0::/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393""" - - // PCF file - "6f265890-5165-7fab-6b52-18d1" | null | 12 | """12:rdma:/ -11:net_cls,net_prio:/garden/6f265890-5165-7fab-6b52-18d1 -10:freezer:/garden/6f265890-5165-7fab-6b52-18d1 -9:devices:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1 -8:blkio:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1 -7:pids:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1 -6:memory:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1 -5:cpuset:/garden/6f265890-5165-7fab-6b52-18d1 -4:cpu,cpuacct:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1 -3:perf_event:/garden/6f265890-5165-7fab-6b52-18d1 -2:hugetlb:/garden/6f265890-5165-7fab-6b52-18d1 -1:name=systemd:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1""" - - // spotless:on - } - - def "ContainerInfo from empty file is empty"() { - when: - File f = File.createTempFile("container-info-test-", "-empty-file") - f.deleteOnExit() - Path p = Paths.get(f.path) - ContainerInfo containerInfo = ContainerInfo.fromProcFile(p) - - - then: - containerInfo.getContainerId() == null - containerInfo.getPodId() == null - containerInfo.getCGroups().size() == 0 - } - - def "ContainerInfo throws java.text.ParseException when given malformed procfile"() { - when: - File f = File.createTempFile("container-info-test-", "-malformed-file") - f.deleteOnExit() - f.write("This is not valid") - Path p = Paths.get(f.path) - ContainerInfo.fromProcFile(p) - - then: - thrown(ParseException) - } - - def "ContainerInfo tolerates missing container id and pod id in procfile"() { - when: - File f = File.createTempFile("container-info-test-", "-missing-container-id") - f.deleteOnExit() - f.write("1:cpuset:fake-path") - Path p = Paths.get(f.path) - ContainerInfo containerInfo = ContainerInfo.fromProcFile(p) - f.deleteOnExit() - - then: - containerInfo.getContainerId() == null - containerInfo.getPodId() == null - containerInfo.getCGroups().size() == 1 - } - - def "getIno(path) should return the same value as `ls -id path`"() { - when: - File f = File.createTempFile("container-info-test-", "-inode-file") - f.deleteOnExit() - Path path = f.toPath() - - then: - ContainerInfo.readInode(path) == readInode(path) - } - - private long readInode(Path path) { - ProcessBuilder pb = new ProcessBuilder("ls", "-id", path.toString()) - Process ps = pb.start() - BufferedReader reader = new BufferedReader(new InputStreamReader(ps.getInputStream())) - String line = reader.readLine() - reader.close() - ps.waitFor() - Long.parseLong(line.substring(0, line.indexOf(' '))) - } - - def "readEntityID return cid- if containerId is defined"() { - when: - ContainerInfo containerInfo = new ContainerInfo() - containerInfo.setContainerId(cid) - - then: - containerInfo.readEntityID(containerInfo, true, Paths.get("/sys/fs/cgroup")) == "cid-" + cid - - where: - cid | isHostCgroupNamespace - "cid" | true - "containerId" | false - } - - def "readEntityID return null if containerId is not defined and isHostCgroupNamespace"() { - when: - ContainerInfo containerInfo = new ContainerInfo() - containerInfo.setContainerId(cid) - - then: - containerInfo.readEntityID(containerInfo, true, Paths.get("/sys/fs/cgroup")) == null - - where: - cid << [null, ""] - } - - def "readEntityID return id- for '' controller"() { - setup: - File mountPath = File.createTempDir("container-info-test-", "-sys-fs-cgroup") - mountPath.deleteOnExit() - File file = File.createTempFile("container-info-test-", "-inode-file", mountPath) - file.deleteOnExit() - Path path = file.toPath() - Long ino = readInode(path) - - when: - ContainerInfo containerInfo = new ContainerInfo() - ContainerInfo.CGroupInfo cGroupInfo = new ContainerInfo.CGroupInfo() - cGroupInfo.setControllers(controllers) - cGroupInfo.setPath(file.getName()) - List cGroups = Arrays.asList(cGroupInfo) - containerInfo.setcGroups(cGroups) - - then: - containerInfo.readEntityID(containerInfo, false, mountPath.toPath()) == (hasEntityId ? "in-" + ino : null) - - where: - controllers | hasEntityId - Arrays.asList("", "memory") | true - Arrays.asList("memory", "") | true - Arrays.asList("") | true - Arrays.asList("memory") | false - } - - def "readEntityID return id- for 'memory' controller"() { - setup: - File mountPath = File.createTempDir("container-info-test-", "-sys-fs-cgroup") - mountPath.deleteOnExit() - File memoryController = Files.createDirectory(mountPath.toPath().resolve("memory")).toFile() - memoryController.deleteOnExit() - File file = File.createTempFile("container-info-test-", "-inode-file", memoryController) - file.deleteOnExit() - Path path = file.toPath() - Long ino = readInode(path) - - when: - ContainerInfo containerInfo = new ContainerInfo() - ContainerInfo.CGroupInfo cGroupInfo = new ContainerInfo.CGroupInfo() - cGroupInfo.setControllers(controllers) - cGroupInfo.setPath(file.getName()) - List cGroups = Arrays.asList(cGroupInfo) - containerInfo.setcGroups(cGroups) - - then: - containerInfo.readEntityID(containerInfo, false, mountPath.toPath()) == (hasEntityId ? "in-" + ino : null) - - where: - controllers | hasEntityId - Arrays.asList("", "memory") | true - Arrays.asList("memory", "") | true - Arrays.asList("memory") | true - Arrays.asList("") | false - } - - def "readEntityID return id- for a parent when path is /"() { - setup: - File mountPath = File.createTempDir("container-info-test-", "-sys-fs-cgroup") - mountPath.deleteOnExit() - File memoryController = Files.createDirectory(mountPath.toPath().resolve("memory")).toFile() - memoryController.deleteOnExit() - Long ino = readInode(memoryController.toPath()) - - when: - ContainerInfo containerInfo = new ContainerInfo() - ContainerInfo.CGroupInfo cGroupInfo = new ContainerInfo.CGroupInfo() - cGroupInfo.setControllers(Arrays.asList("memory")) - cGroupInfo.setPath("/") - List cGroups = Arrays.asList(cGroupInfo) - containerInfo.setcGroups(cGroups) - - then: - containerInfo.readEntityID(containerInfo, false, mountPath.toPath()) == "in-" + ino - } -} diff --git a/utils/container-utils/src/test/groovy/datadog/common/container/ServerlessInfoTest.groovy b/utils/container-utils/src/test/groovy/datadog/common/container/ServerlessInfoTest.groovy deleted file mode 100644 index 329fc8b2fc4..00000000000 --- a/utils/container-utils/src/test/groovy/datadog/common/container/ServerlessInfoTest.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package datadog.common.container - -import datadog.trace.test.util.DDSpecification - -class ServerlessInfoTest extends DDSpecification { - - def "test serverless detection"() { - given: - environmentVariables.set(ServerlessInfo.AWS_FUNCTION_VARIABLE, functionName) - - when: - def info = new ServerlessInfo() - - then: - info.runningInServerlessEnvironment == serverlessEnv - info.functionName == functionName - - where: - functionName | serverlessEnv - null | false - "" | false - "someName" | true - } - - def "test serverless hasExtension false"() { - when: - def info = new ServerlessInfo() - then: - info.hasExtension() == false - } - - def "test serverless hasExtension false since the extension path is null"() { - when: - def info = new ServerlessInfo(null) - then: - info.hasExtension() == false - } - - def "test serverless hasExtension true"() { - when: - File f = File.createTempFile("fake-", "extension") - f.deleteOnExit() - def info = new ServerlessInfo(f.getAbsolutePath()) - then: - info.hasExtension() == true - } -} diff --git a/utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java b/utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java new file mode 100644 index 00000000000..c429736fbe7 --- /dev/null +++ b/utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java @@ -0,0 +1,377 @@ +package datadog.common.container; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import datadog.trace.test.util.DDJavaSpecification; +import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.tabletest.junit.TableTest; + +@SuppressForbidden +public class ContainerInfoTest extends DDJavaSpecification { + + // spotless:off + @TableTest({ + "id | controllers | path | containerId | podId | line", + // Docker examples + "13 | [name=systemd] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + "12 | [pids] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + "11 | [hugetlb] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + "10 | [net_prio] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 9 | [perf_event] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 8 | [net_cls] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 7 | [freezer] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 6 | [devices] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 5 | [memory] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 4 | [blkio] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 3 | [cpuacct] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 2 | [cpu] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + " 1 | [cpuset] | /docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | 3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 | | 1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", + // Kubernetes examples + "11 | [perf_event] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + "10 | [pids] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 9 | [memory] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 8 | [cpu, cpuacct] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 7 | [blkio] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 6 | [cpuset] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 5 | [devices] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 4 | [freezer] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 3 | [net_cls, net_prio] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 2 | [hugetlb] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + " 1 | [name=systemd] | /kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 | 3d274242-8ee0-11e9-a8a6-1e68d864ef1a | 1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", + // ECS examples + " 9 | [perf_event] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 8 | [memory] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 7 | [hugetlb] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 6 | [freezer] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 5 | [devices] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 4 | [cpuset] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 3 | [cpuacct] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 2 | [cpu] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + " 1 | [blkio] | /ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | 38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce | | 1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", + // Fargate examples + "11 | [hugetlb] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + "10 | [pids] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 9 | [cpuset] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 8 | [net_cls, net_prio] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 7 | [cpu, cpuacct] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 6 | [perf_event] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 5 | [freezer] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 4 | [devices] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 3 | [blkio] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 2 | [memory] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 1 | [name=systemd] | /ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | 432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da | | 1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", + " 1 | [name=systemd] | /ecs/34dc0b5e626f2c5c4c5170e34b10e765-1234567890 | 34dc0b5e626f2c5c4c5170e34b10e765-1234567890 | | 1:name=systemd:/ecs/34dc0b5e626f2c5c4c5170e34b10e765-1234567890", + // PCF example + " 1 | [freezer] | /garden/6f265890-5165-7fab-6b52-18d1 | 6f265890-5165-7fab-6b52-18d1 | | 1:freezer:/garden/6f265890-5165-7fab-6b52-18d1", + // Reference impl examples + " 1 | [name=systemd] | /system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope | cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411 | | 1:name=systemd:/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope", + " 1 | [name=systemd] | /docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex | | | 1:name=systemd:/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex", + " 1 | [name=systemd] | /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope | 2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63 | 90d81341_92de_11e7_8cf2_507b9d4141fa | 1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope" + }) + void cGroupInfoIsParsedFromIndividualLines( + int id, List controllers, String path, String containerId, String podId, String line) + throws ParseException { + ContainerInfo.CGroupInfo cGroupInfo = ContainerInfo.parseLine(line); + + assertEquals(id, cGroupInfo.getId()); + assertEquals(path, cGroupInfo.getPath()); + assertEquals(controllers, cGroupInfo.getControllers()); + assertEquals(containerId, cGroupInfo.getContainerId()); + assertEquals(podId, cGroupInfo.getPodId()); + } + // spotless:on + + @ParameterizedTest + @MethodSource("containerInfoParsedFromFileContentArguments") + void containerInfoParsedFromFileContent( + String containerId, String podId, int size, String content) throws Exception { + ContainerInfo containerInfo = ContainerInfo.parse(content); + + assertEquals(containerId, containerInfo.getContainerId()); + assertEquals(podId, containerInfo.getPodId()); + assertEquals(size, containerInfo.getCGroups().size()); + } + + static Stream containerInfoParsedFromFileContentArguments() { + // spotless:off + return Stream.of( + // Docker + arguments("3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860", null, 13, + "13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860\n" + + "1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"), + // Kubernetes + arguments("3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1", "3d274242-8ee0-11e9-a8a6-1e68d864ef1a", 11, + "11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1\n" + + "1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"), + arguments("7b8952daecf4c0e44bbcefe1b5c5ebc7b4839d4eefeccefe694709d3809b6199", "2d3da189_6407_48e3_9ab6_78188d75e609", 1, + "1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod2d3da189_6407_48e3_9ab6_78188d75e609.slice/docker-7b8952daecf4c0e44bbcefe1b5c5ebc7b4839d4eefeccefe694709d3809b6199.scope"), + // ECS + arguments("38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce", null, 9, + "9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce\n" + + "1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"), + // Fargate 1.3- + arguments("432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da", null, 11, + "11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"), + // Fargate 1.4+ + arguments("34dc0b5e626f2c5c4c5170e34b10e765-1234567890", null, 11, + "11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da\n" + + "1:name=systemd:/ecs/34dc0b5e626f2c5c4c5170e34b10e765-1234567890"), + // EKS Fargate with trailing cgroup v2 membership entry + arguments("cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc", "defa568d-ff14-43d9-9a63-9e39ee9b39b4", 13, + "12:misc:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "11:cpuset:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "10:perf_event:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "9:blkio:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "8:net_cls,net_prio:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "7:memory:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "6:cpu,cpuacct:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "5:pids:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "4:devices:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "3:hugetlb:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "2:freezer:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "1:name=systemd:/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393/kubepods/burstable/poddefa568d-ff14-43d9-9a63-9e39ee9b39b4/cf1241bbf80ea91eebdd28bf719057380997ca4b0cea16869393b905fb6d52bc\n" + + "0::/ecs/545b896a072744d186c7fb09a45ec172/545b896a072744d186c7fb09a45ec172-3057940393"), + // PCF + arguments("6f265890-5165-7fab-6b52-18d1", null, 12, + "12:rdma:/\n" + + "11:net_cls,net_prio:/garden/6f265890-5165-7fab-6b52-18d1\n" + + "10:freezer:/garden/6f265890-5165-7fab-6b52-18d1\n" + + "9:devices:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1\n" + + "8:blkio:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1\n" + + "7:pids:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1\n" + + "6:memory:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1\n" + + "5:cpuset:/garden/6f265890-5165-7fab-6b52-18d1\n" + + "4:cpu,cpuacct:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1\n" + + "3:perf_event:/garden/6f265890-5165-7fab-6b52-18d1\n" + + "2:hugetlb:/garden/6f265890-5165-7fab-6b52-18d1\n" + + "1:name=systemd:/system.slice/garden.service/garden/6f265890-5165-7fab-6b52-18d1") + ); + // spotless:on + } + + @Test + void containerInfoFromEmptyFileIsEmpty() throws Exception { + File f = File.createTempFile("container-info-test-", "-empty-file"); + f.deleteOnExit(); + Path p = Paths.get(f.getPath()); + + ContainerInfo containerInfo = ContainerInfo.fromProcFile(p); + + assertNull(containerInfo.getContainerId()); + assertNull(containerInfo.getPodId()); + assertEquals(0, containerInfo.getCGroups().size()); + } + + @Test + void containerInfoThrowsParseExceptionWhenGivenMalformedProcfile() throws Exception { + File f = File.createTempFile("container-info-test-", "-malformed-file"); + f.deleteOnExit(); + Files.write(f.toPath(), "This is not valid".getBytes()); + Path p = Paths.get(f.getPath()); + + assertThrows(ParseException.class, () -> ContainerInfo.fromProcFile(p)); + } + + @Test + void containerInfoToleratesMissingContainerIdAndPodIdInProcfile() throws Exception { + File f = File.createTempFile("container-info-test-", "-missing-container-id"); + f.deleteOnExit(); + Files.write(f.toPath(), "1:cpuset:fake-path".getBytes()); + Path p = Paths.get(f.getPath()); + + ContainerInfo containerInfo = ContainerInfo.fromProcFile(p); + + assertNull(containerInfo.getContainerId()); + assertNull(containerInfo.getPodId()); + assertEquals(1, containerInfo.getCGroups().size()); + } + + @Test + void getInoPathShouldReturnSameValueAsLsIdPath() throws Exception { + File f = File.createTempFile("container-info-test-", "-inode-file"); + f.deleteOnExit(); + Path path = f.toPath(); + + assertEquals(readInode(path), ContainerInfo.readInode(path)); + } + + // spotless:off + @TableTest({ + "cid | isHostCgroupNamespace", + "cid | true ", + "containerId | false " + }) + void readEntityIDReturnCidContainerIdIfContainerIdIsDefined( + String cid, boolean isHostCgroupNamespace) { + ContainerInfo containerInfo = new ContainerInfo(); + containerInfo.setContainerId(cid); + + assertEquals("cid-" + cid, + ContainerInfo.readEntityID(containerInfo, true, Paths.get("/sys/fs/cgroup"))); + } + // spotless:on + + @TableTest({ + "cid", + " ", + "''" + }) + void readEntityIDReturnNullIfContainerIdIsNotDefinedAndIsHostCgroupNamespace(String cid) { + ContainerInfo containerInfo = new ContainerInfo(); + containerInfo.setContainerId(cid); + + assertNull(ContainerInfo.readEntityID(containerInfo, true, Paths.get("/sys/fs/cgroup"))); + } + + // spotless:off + @TableTest({ + "controllers | hasEntityId", + "['', memory] | true ", + "[memory, ''] | true ", + "[''] | true ", + "[memory] | false " + }) + void readEntityIDReturnIdInoForEmptyController( + List controllers, boolean hasEntityId) throws Exception { + File mountPath = createTempDir(); + File file = File.createTempFile("container-info-test-", "-inode-file", mountPath); + file.deleteOnExit(); + long ino = readInode(file.toPath()); + + ContainerInfo containerInfo = new ContainerInfo(); + ContainerInfo.CGroupInfo cGroupInfo = new ContainerInfo.CGroupInfo(); + cGroupInfo.setControllers(controllers); + cGroupInfo.setPath(file.getName()); + containerInfo.setcGroups(Arrays.asList(cGroupInfo)); + + String expected = hasEntityId ? "in-" + ino : null; + assertEquals(expected, ContainerInfo.readEntityID(containerInfo, false, mountPath.toPath())); + } + + @TableTest({ + "controllers | hasEntityId", + "['', memory] | true ", + "[memory, ''] | true ", + "[memory] | true ", + "[''] | false " + }) + void readEntityIDReturnIdInoForMemoryController( + List controllers, boolean hasEntityId) throws Exception { + File mountPath = createTempDir(); + File memoryController = + Files.createDirectory(mountPath.toPath().resolve("memory")).toFile(); + memoryController.deleteOnExit(); + File file = File.createTempFile("container-info-test-", "-inode-file", memoryController); + file.deleteOnExit(); + long ino = readInode(file.toPath()); + + ContainerInfo containerInfo = new ContainerInfo(); + ContainerInfo.CGroupInfo cGroupInfo = new ContainerInfo.CGroupInfo(); + cGroupInfo.setControllers(controllers); + cGroupInfo.setPath(file.getName()); + containerInfo.setcGroups(Arrays.asList(cGroupInfo)); + + String expected = hasEntityId ? "in-" + ino : null; + assertEquals(expected, ContainerInfo.readEntityID(containerInfo, false, mountPath.toPath())); + } + // spotless:on + + @Test + void readEntityIDReturnIdInoForParentWhenPathIsSlash() throws Exception { + File mountPath = createTempDir(); + File memoryController = + Files.createDirectory(mountPath.toPath().resolve("memory")).toFile(); + memoryController.deleteOnExit(); + long ino = readInode(memoryController.toPath()); + + ContainerInfo containerInfo = new ContainerInfo(); + ContainerInfo.CGroupInfo cGroupInfo = new ContainerInfo.CGroupInfo(); + cGroupInfo.setControllers(Arrays.asList("memory")); + cGroupInfo.setPath("/"); + containerInfo.setcGroups(Arrays.asList(cGroupInfo)); + + assertEquals("in-" + ino, + ContainerInfo.readEntityID(containerInfo, false, mountPath.toPath())); + } + + private static File createTempDir() throws IOException { + File dir = File.createTempFile("container-info-test-", "-sys-fs-cgroup"); + dir.delete(); + dir.mkdirs(); + dir.deleteOnExit(); + return dir; + } + + private static long readInode(Path path) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder("ls", "-id", path.toString()); + Process ps = pb.start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(ps.getInputStream()))) { + String line = reader.readLine(); + ps.waitFor(); + return Long.parseLong(line.substring(0, line.indexOf(' '))); + } + } +} diff --git a/utils/container-utils/src/test/java/datadog/common/container/ServerlessInfoTest.java b/utils/container-utils/src/test/java/datadog/common/container/ServerlessInfoTest.java new file mode 100644 index 00000000000..65d0eb84118 --- /dev/null +++ b/utils/container-utils/src/test/java/datadog/common/container/ServerlessInfoTest.java @@ -0,0 +1,72 @@ +package datadog.common.container; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.test.util.ControllableEnvironmentVariables; +import datadog.trace.test.util.DDJavaSpecification; +import java.io.File; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.tabletest.junit.TableTest; + +public class ServerlessInfoTest extends DDJavaSpecification { + + // AWS_LAMBDA_FUNCTION_NAME is ServerlessInfo.AWS_FUNCTION_VARIABLE (private constant) + private static final String AWS_FUNCTION_VARIABLE = "AWS_LAMBDA_FUNCTION_NAME"; + + private static final ControllableEnvironmentVariables environmentVariables = + ControllableEnvironmentVariables.setup(); + + @AfterEach + void clearEnvVars() { + environmentVariables.clear(); + } + + @TableTest({ + "functionName | serverlessEnv", + " | false ", + "'' | false ", + "someName | true " + }) + void testServerlessDetection(String functionName, boolean serverlessEnv) { + environmentVariables.set(AWS_FUNCTION_VARIABLE, functionName); + + ServerlessInfo info = new ServerlessInfo(); + + assertEquals(serverlessEnv, info.isRunningInServerlessEnvironment()); + assertEquals(functionName, info.getFunctionName()); + } + + @Test + void testServerlessHasExtensionFalse() { + ServerlessInfo info = new ServerlessInfo(); + + assertFalse(info.hasExtension()); + } + + @Test + void testServerlessHasExtensionFalseSinceExtensionPathIsNull() throws Exception { + // ServerlessInfo(String extensionPath) is private — access via reflection + java.lang.reflect.Constructor constructor = + ServerlessInfo.class.getDeclaredConstructor(String.class); + constructor.setAccessible(true); + ServerlessInfo info = constructor.newInstance((Object) null); + + assertFalse(info.hasExtension()); + } + + @Test + void testServerlessHasExtensionTrue() throws Exception { + File f = File.createTempFile("fake-", "extension"); + f.deleteOnExit(); + + java.lang.reflect.Constructor constructor = + ServerlessInfo.class.getDeclaredConstructor(String.class); + constructor.setAccessible(true); + ServerlessInfo info = constructor.newInstance(f.getAbsolutePath()); + + assertTrue(info.hasExtension()); + } +} diff --git a/utils/time-utils/src/test/groovy/datadog/trace/api/time/TimeUtilsTest.groovy b/utils/time-utils/src/test/groovy/datadog/trace/api/time/TimeUtilsTest.groovy deleted file mode 100644 index 0a569b57c93..00000000000 --- a/utils/time-utils/src/test/groovy/datadog/trace/api/time/TimeUtilsTest.groovy +++ /dev/null @@ -1,50 +0,0 @@ -package datadog.trace.api.time - -import datadog.trace.test.util.DDSpecification - -class TimeUtilsTest extends DDSpecification { - - def "test simple delay parsing"() { - when: - long delay = TimeUtils.parseSimpleDelay(delayString) - - then: - delay == expected - - where: - delayString | expected - null | -1 - "" | -1 - "foo" | -1 - "-8" | -1 - "-1" | -1 - "0" | 0 - "1" | 1 - "2" | 2 - "3" | 3 - "0s" | 0 - "1s" | 1 - "2s" | 2 - "3s" | 3 - "0m" | 0 - "1m" | 60 - "2m" | 120 - "3m" | 180 - "0h" | 0 - "1h" | 3600 - "2h" | 7200 - "3h" | 10800 - "0S" | 0 - "1S" | 1 - "2S" | 2 - "3S" | 3 - "0M" | 0 - "1M" | 60 - "2M" | 120 - "3M" | 180 - "0H" | 0 - "1H" | 3600 - "2H" | 7200 - "3H" | 10800 - } -} diff --git a/utils/time-utils/src/test/java/datadog/trace/api/time/TimeUtilsTest.java b/utils/time-utils/src/test/java/datadog/trace/api/time/TimeUtilsTest.java new file mode 100644 index 00000000000..1b105d78f4d --- /dev/null +++ b/utils/time-utils/src/test/java/datadog/trace/api/time/TimeUtilsTest.java @@ -0,0 +1,51 @@ +package datadog.trace.api.time; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import datadog.trace.test.util.DDJavaSpecification; +import org.tabletest.junit.TableTest; + +public class TimeUtilsTest extends DDJavaSpecification { + + // spotless:off + @TableTest({ + "delayString | expected", + " | -1 ", + "'' | -1 ", + "foo | -1 ", + "-8 | -1 ", + "-1 | -1 ", + "0 | 0 ", + "1 | 1 ", + "2 | 2 ", + "3 | 3 ", + "0s | 0 ", + "1s | 1 ", + "2s | 2 ", + "3s | 3 ", + "0m | 0 ", + "1m | 60 ", + "2m | 120 ", + "3m | 180 ", + "0h | 0 ", + "1h | 3600 ", + "2h | 7200 ", + "3h | 10800 ", + "0S | 0 ", + "1S | 1 ", + "2S | 2 ", + "3S | 3 ", + "0M | 0 ", + "1M | 60 ", + "2M | 120 ", + "3M | 180 ", + "0H | 0 ", + "1H | 3600 ", + "2H | 7200 ", + "3H | 10800 " + }) + // spotless:on + void testSimpleDelayParsing(String delayString, long expected) { + assertEquals(expected, TimeUtils.parseSimpleDelay(delayString)); + } +} From ff836e044cce28f081d0ac609594ca069f9a9a58 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 4 Jun 2026 15:16:53 -0400 Subject: [PATCH 5/6] Clean up --- .../java/datadog/trace/api/ConfigSettingTest.java | 15 +++++++-------- .../common/container/ContainerInfoTest.java | 15 +++++---------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java index 3b361435c3d..a587a992fb6 100644 --- a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java +++ b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java @@ -18,11 +18,11 @@ public class ConfigSettingTest { @TableTest({ - "scenario | key1 | key2 | value1 | value2 | origin1 | origin2 | expectedEqual", - "equal | key | key | value | value | DEFAULT | DEFAULT | true ", - "different key | key | key2 | value | value | ENV | ENV | false ", - "different value | key | key | value2 | value | JVM_PROP | JVM_PROP | false ", - "different origin | key | key | value | value | ENV | DEFAULT | false " + "scenario | key1 | key2 | value1 | value2 | origin1 | origin2 ", + "equal | key | key | value | value | DEFAULT | DEFAULT ", + "different key | key | key2 | value | value | ENV | ENV ", + "different value | key | key | value2 | value | JVM_PROP | JVM_PROP", + "different origin | key | key | value | value | ENV | DEFAULT " }) void supportsEqualityCheck( String key1, @@ -30,14 +30,13 @@ void supportsEqualityCheck( Object value1, Object value2, ConfigOrigin origin1, - ConfigOrigin origin2, - boolean expectedEqual) { + ConfigOrigin origin2) { // when ConfigSetting cs1 = ConfigSetting.of(key1, value1, origin1); ConfigSetting cs2 = ConfigSetting.of(key2, value2, origin2); // then - if (expectedEqual) { + if (key1.equals(key2) && value1.equals(value2) && origin1 == origin2) { assertEquals(cs1.hashCode(), cs2.hashCode()); assertEquals(cs1, cs2); assertEquals(cs2, cs1); diff --git a/utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java b/utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java index c429736fbe7..6d45cb4a7bb 100644 --- a/utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java +++ b/utils/container-utils/src/test/java/datadog/common/container/ContainerInfoTest.java @@ -109,7 +109,8 @@ void containerInfoParsedFromFileContent( assertEquals(size, containerInfo.getCGroups().size()); } - static Stream containerInfoParsedFromFileContentArguments() { + static Stream + containerInfoParsedFromFileContentArguments() { // spotless:off return Stream.of( // Docker @@ -274,11 +275,7 @@ void readEntityIDReturnCidContainerIdIfContainerIdIsDefined( } // spotless:on - @TableTest({ - "cid", - " ", - "''" - }) + @TableTest({"cid", " ", "'' "}) void readEntityIDReturnNullIfContainerIdIsNotDefinedAndIsHostCgroupNamespace(String cid) { ContainerInfo containerInfo = new ContainerInfo(); containerInfo.setContainerId(cid); @@ -342,8 +339,7 @@ void readEntityIDReturnIdInoForMemoryController( @Test void readEntityIDReturnIdInoForParentWhenPathIsSlash() throws Exception { File mountPath = createTempDir(); - File memoryController = - Files.createDirectory(mountPath.toPath().resolve("memory")).toFile(); + File memoryController = Files.createDirectory(mountPath.toPath().resolve("memory")).toFile(); memoryController.deleteOnExit(); long ino = readInode(memoryController.toPath()); @@ -353,8 +349,7 @@ void readEntityIDReturnIdInoForParentWhenPathIsSlash() throws Exception { cGroupInfo.setPath("/"); containerInfo.setcGroups(Arrays.asList(cGroupInfo)); - assertEquals("in-" + ino, - ContainerInfo.readEntityID(containerInfo, false, mountPath.toPath())); + assertEquals("in-" + ino, ContainerInfo.readEntityID(containerInfo, false, mountPath.toPath())); } private static File createTempDir() throws IOException { From 54ccc2aa75be5537b5d65185291dcf3dec3db521 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 4 Jun 2026 15:41:50 -0400 Subject: [PATCH 6/6] Use Collections.emptyMap instead of empty LinkedHashMap --- .../src/test/java/datadog/trace/api/ConfigSettingTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java index a587a992fb6..1f9563179c2 100644 --- a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java +++ b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java @@ -7,6 +7,7 @@ import datadog.trace.junit.utils.tabletest.BoxedValueConverter; import java.util.Arrays; import java.util.BitSet; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Stream; @@ -97,7 +98,7 @@ void convertIterableMapAndBitSetToString(Object value, String rendered) { arguments(Arrays.asList(1.0f, 22.23d, 3.1415d), "1.0,22.23,3.1415"), arguments(mapStringInt, "a:1,b:2"), arguments(mapStringString, "a:1,b:2"), - arguments(new LinkedHashMap<>(), ""), + arguments(Collections.emptyMap(), ""), arguments(Arrays.asList(), ""), arguments(bitSetIntervals(), "33,200-300,303,400-500")); }