Skip to content

Commit d75d171

Browse files
authored
Add explicit delimiters to node configs list (#12444)
* Add explicit delimiter to node configs list * Remove sorting of flattened map entries
1 parent d7e1f84 commit d75d171

File tree

5 files changed

+140
-147
lines changed

5 files changed

+140
-147
lines changed

java/src/org/openqa/selenium/grid/config/Config.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.Optional;
2424
import java.util.Set;
2525
import java.util.logging.Logger;
26+
import java.util.stream.Collectors;
27+
2628
import org.openqa.selenium.json.Json;
2729

2830
public interface Config {
@@ -59,14 +61,21 @@ default <X> X getClass(String section, String option, Class<X> typeOfClass, Stri
5961
}
6062
}
6163

64+
String DELIM_KEY = "\u001E";
65+
String DELIMITER = DELIM_KEY + "=\"record-separator\"";
66+
6267
default List<String> toEntryList(Map<String, Object> mapItem) {
63-
return mapItem.entrySet().stream()
64-
.map(
65-
entry -> {
66-
return String.format("%s=%s", entry.getKey(), toJson(entry.getValue()));
67-
})
68-
.sorted()
69-
.collect(ImmutableList.toImmutableList());
68+
// transform config settings map into list of key/value strings
69+
List<String> entryList = mapItem.entrySet().stream()
70+
.map(
71+
entry -> {
72+
return String.format("%s=%s", entry.getKey(), toJson(entry.getValue()));
73+
})
74+
.collect(Collectors.toList());
75+
// add record separator
76+
entryList.add(DELIMITER);
77+
// return immutable config settings list
78+
return ImmutableList.<String>builder().addAll(entryList).build();
7079
}
7180

7281
default String toJson(Object value) {

java/src/org/openqa/selenium/grid/node/config/NodeOptions.java

Lines changed: 112 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -344,143 +344,124 @@ private SessionFactory createSessionFactory(String clazz, Capabilities stereotyp
344344
private void addDriverConfigs(
345345
Function<ImmutableCapabilities, Collection<SessionFactory>> factoryFactory,
346346
ImmutableMultimap.Builder<Capabilities, SessionFactory> sessionFactories) {
347-
Multimap<WebDriverInfo, SessionFactory> driverConfigs = HashMultimap.create();
348-
config
349-
.getAll(NODE_SECTION, "driver-configuration")
350-
.ifPresent(
351-
drivers -> {
352-
/*
353-
The four accepted keys are: display-name, max-sessions, stereotype, webdriver-executable.
354-
The mandatory keys are display-name and stereotype. When configs are read, they keys always
355-
come alphabetically ordered. This means that we know a new config is present when we find
356-
the "display-name" key again.
357-
*/
358-
359-
if (drivers.size() == 0) {
360-
throw new ConfigException("No driver configs were found!");
361-
}
362347

363-
drivers.stream()
364-
.filter(driver -> !driver.contains("="))
365-
.peek(
366-
driver ->
367-
LOG.warning(
368-
driver
369-
+ " does not have the required 'key=value' "
370-
+ "structure for the configuration"))
371-
.findFirst()
372-
.ifPresent(
373-
ignore -> {
374-
throw new ConfigException(
375-
"One or more driver configs does not have the "
376-
+ "required 'key=value' structure");
377-
});
378-
379-
// Find all indexes where "display-name" is present, as it marks the start of a config
380-
int[] configIndexes =
381-
IntStream.range(0, drivers.size())
382-
.filter(index -> drivers.get(index).startsWith("display-name"))
383-
.toArray();
384-
385-
if (configIndexes.length == 0) {
386-
throw new ConfigException(
387-
"No 'display-name' keyword was found in the provided configs!");
388-
}
389-
390-
List<Map<String, String>> driversMap = new ArrayList<>();
391-
for (int i = 0; i < configIndexes.length; i++) {
392-
int fromIndex = configIndexes[i];
393-
int toIndex =
394-
(i + 1) >= configIndexes.length ? drivers.size() : configIndexes[i + 1];
395-
Map<String, String> configMap = new HashMap<>();
396-
drivers
397-
.subList(fromIndex, toIndex)
398-
.forEach(
399-
keyValue -> {
400-
String[] values = keyValue.split("=", 2);
401-
configMap.put(values[0], unquote(values[1]));
402-
});
403-
driversMap.add(configMap);
404-
}
348+
Multimap<WebDriverInfo, SessionFactory> driverConfigs = HashMultimap.create();
405349

406-
List<DriverService.Builder<?, ?>> builders = new ArrayList<>();
407-
ServiceLoader.load(DriverService.Builder.class).forEach(builders::add);
350+
// get all driver configuration settings
351+
config.getAll(NODE_SECTION, "driver-configuration")
352+
// if settings exist
353+
.ifPresent(drivers -> {
354+
Map<String, String> configMap = new HashMap<>();
355+
List<Map<String, String>> configList = new ArrayList<>();
356+
357+
// iterate over driver settings
358+
for (String setting : drivers) {
359+
// split this setting into key/value pair
360+
String[] values = setting.split("=", 2);
361+
// if format is invalid
362+
if (values.length != 2) {
363+
throw new ConfigException("Driver setting '" + setting
364+
+ "' does not adhere to the required 'key=value' format!");
365+
}
366+
// if this is a record separator
367+
if (values[0].equals(Config.DELIM_KEY)) {
368+
// if config lacks settings
369+
if (configMap.isEmpty()) {
370+
throw new ConfigException("Found config delimiter with no preceding settings!");
371+
}
372+
373+
// if config lacks 'display-name' setting
374+
if (!configMap.containsKey("display-name")) {
375+
throw new ConfigException("Found config with no 'display-name' setting! " + configMap);
376+
}
377+
378+
// if config lacks 'stereotype' setting
379+
if (!configMap.containsKey("stereotype")) {
380+
throw new ConfigException("Found config with no 'stereotype' setting! " + configMap);
381+
}
382+
383+
// add config to list
384+
configList.add(configMap);
385+
// prepare for next config
386+
configMap = new HashMap<>();
387+
} else {
388+
// add setting to config
389+
configMap.put(values[0], unquote(values[1]));
390+
}
391+
}
408392

409-
List<WebDriverInfo> infos = new ArrayList<>();
410-
ServiceLoader.load(WebDriverInfo.class).forEach(infos::add);
393+
// if no configs were found
394+
if (configList.isEmpty()) {
395+
throw new ConfigException("No driver configs were found!");
396+
}
411397

412-
driversMap.forEach(
413-
configMap -> {
414-
if (!configMap.containsKey("stereotype")) {
415-
throw new ConfigException(
416-
"Driver config is missing stereotype value. " + configMap);
417-
}
398+
List<DriverService.Builder<?, ?>> builderList = new ArrayList<>();
399+
ServiceLoader.load(DriverService.Builder.class).forEach(builderList::add);
400+
401+
List<WebDriverInfo> infoList = new ArrayList<>();
402+
ServiceLoader.load(WebDriverInfo.class).forEach(infoList::add);
403+
404+
// iterate over driver configs
405+
configList.forEach(thisConfig -> {
406+
// create Capabilities object from stereotype of this config
407+
Capabilities confStereotype = JSON.toType(thisConfig.get("stereotype"), Capabilities.class);
408+
409+
// extract driver executable path from this config
410+
String webDriverExecutablePath = thisConfig.get("webdriver-executable");
411+
// if executable path is specified
412+
if (null != webDriverExecutablePath) {
413+
// create File object from executable path string
414+
File webDriverExecutable = new File(webDriverExecutablePath);
415+
// if specified path isn't a file
416+
if (!webDriverExecutable.isFile()) {
417+
LOG.warning("Driver executable does not seem to be a file! " + webDriverExecutablePath);
418+
}
419+
420+
// if specified path isn't executable
421+
if (!webDriverExecutable.canExecute()) {
422+
LOG.warning("Driver file exists but does not seem to be a executable! " + webDriverExecutablePath);
423+
}
424+
425+
// add specified driver executable path to capabilities
426+
confStereotype = new PersistentCapabilities(confStereotype)
427+
.setCapability("se:webDriverExecutable", webDriverExecutablePath);
428+
}
429+
430+
Capabilities stereotype = enhanceStereotype(confStereotype);
431+
String configName = thisConfig.getOrDefault("display-name", "Custom Slot Config");
432+
433+
WebDriverInfo info = infoList.stream()
434+
.filter(webDriverInfo -> webDriverInfo.isSupporting(stereotype))
435+
.findFirst()
436+
.orElseThrow(() ->
437+
new ConfigException("Unable to find matching driver for %s", stereotype));
438+
439+
int driverMaxSessions = Integer.parseInt(thisConfig.getOrDefault(
440+
"max-sessions", String.valueOf(info.getMaximumSimultaneousSessions())));
441+
Require.positive("Driver max sessions", driverMaxSessions);
442+
443+
WebDriverInfo driverInfoConfig = createConfiguredDriverInfo(info, stereotype, configName);
444+
445+
builderList.stream()
446+
.filter(builder -> builder.score(stereotype) > 0)
447+
.max(Comparator.comparingInt(builder -> builder.score(stereotype)))
448+
.ifPresent(builder -> {
449+
ImmutableCapabilities immutable = new ImmutableCapabilities(stereotype);
450+
int maxDriverSessions = getDriverMaxSessions(info, driverMaxSessions);
451+
for (int i = 0; i < maxDriverSessions; i++) {
452+
driverConfigs.putAll(driverInfoConfig, factoryFactory.apply(immutable));
453+
}
454+
}
455+
);
456+
}
457+
);
458+
}
459+
);
418460

419-
Capabilities confStereotype =
420-
JSON.toType(configMap.get("stereotype"), Capabilities.class);
421-
if (configMap.containsKey("webdriver-executable")) {
422-
String webDriverExecutablePath =
423-
configMap.getOrDefault("webdriver-executable", "");
424-
File webDriverExecutable = new File(webDriverExecutablePath);
425-
if (!webDriverExecutable.isFile()) {
426-
LOG.warning(
427-
"Driver executable does not seem to be a file! "
428-
+ webDriverExecutablePath);
429-
}
430-
if (!webDriverExecutable.canExecute()) {
431-
LOG.warning(
432-
"Driver file exists but does not seem to be a executable! "
433-
+ webDriverExecutablePath);
434-
}
435-
confStereotype =
436-
new PersistentCapabilities(confStereotype)
437-
.setCapability("se:webDriverExecutable", webDriverExecutablePath);
438-
}
439-
Capabilities stereotype = enhanceStereotype(confStereotype);
440-
441-
String configName =
442-
configMap.getOrDefault("display-name", "Custom Slot Config");
443-
444-
WebDriverInfo info =
445-
infos.stream()
446-
.filter(webDriverInfo -> webDriverInfo.isSupporting(stereotype))
447-
.findFirst()
448-
.orElseThrow(
449-
() ->
450-
new ConfigException(
451-
"Unable to find matching driver for %s", stereotype));
452-
453-
int driverMaxSessions =
454-
Integer.parseInt(
455-
configMap.getOrDefault(
456-
"max-sessions",
457-
String.valueOf(info.getMaximumSimultaneousSessions())));
458-
Require.positive("Driver max sessions", driverMaxSessions);
459-
460-
WebDriverInfo driverInfoConfig =
461-
createConfiguredDriverInfo(info, stereotype, configName);
462-
463-
builders.stream()
464-
.filter(builder -> builder.score(stereotype) > 0)
465-
.max(Comparator.comparingInt(builder -> builder.score(stereotype)))
466-
.ifPresent(
467-
builder -> {
468-
ImmutableCapabilities immutable =
469-
new ImmutableCapabilities(stereotype);
470-
int maxDriverSessions = getDriverMaxSessions(info, driverMaxSessions);
471-
for (int i = 0; i < maxDriverSessions; i++) {
472-
driverConfigs.putAll(
473-
driverInfoConfig, factoryFactory.apply(immutable));
474-
}
475-
});
476-
});
477-
});
478461
driverConfigs.asMap().entrySet().stream()
479-
.peek(this::report)
480-
.forEach(
481-
entry ->
482-
sessionFactories.putAll(
483-
entry.getKey().getCanonicalCapabilities(), entry.getValue()));
462+
.peek(this::report)
463+
.forEach(entry ->
464+
sessionFactories.putAll(entry.getKey().getCanonicalCapabilities(), entry.getValue()));
484465
}
485466

486467
private void addDetectedDrivers(

java/test/org/openqa/selenium/grid/config/JsonConfigTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ void shouldContainConfigFromArrayOfTables() {
8383

8484
List<String> expected =
8585
Arrays.asList(
86-
"default=\"brie\"", "name=\"soft cheese\"",
87-
"default=\"Emmental\"", "name=\"Medium-hard cheese\"");
86+
"name=\"soft cheese\"", "default=\"brie\"", Config.DELIMITER,
87+
"name=\"Medium-hard cheese\"", "default=\"Emmental\"", Config.DELIMITER);
8888
assertThat(config.getAll("cheeses", "type").orElse(Collections.emptyList()))
8989
.isEqualTo(expected);
9090
assertThat(config.getAll("cheeses", "type").orElse(Collections.emptyList()).subList(0, 2))
@@ -116,7 +116,8 @@ void ensureCanReadListOfMaps() {
116116
List<String> expected =
117117
Arrays.asList(
118118
"display-name=\"htmlunit\"",
119-
"stereotype={\"browserName\": \"htmlunit\",\"browserVersion\": \"chrome\"}");
119+
"stereotype={\"browserName\": \"htmlunit\",\"browserVersion\": \"chrome\"}",
120+
Config.DELIMITER);
120121
Optional<List<String>> content = config.getAll("node", "driver-configuration");
121122
assertThat(content).isEqualTo(Optional.of(expected));
122123
}

java/test/org/openqa/selenium/grid/config/MapConfigTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ void shouldContainConfigFromArrayOfTables() {
6868

6969
List<String> expected =
7070
Arrays.asList(
71-
"default=\"brie\"", "name=\"soft cheese\"",
72-
"default=\"Emmental\"", "name=\"Medium-hard cheese\"");
71+
"name=\"soft cheese\"", "default=\"brie\"", Config.DELIMITER,
72+
"name=\"Medium-hard cheese\"", "default=\"Emmental\"", Config.DELIMITER);
7373
assertThat(config.getAll("cheeses", "type").orElse(Collections.emptyList()))
7474
.isEqualTo(expected);
7575
assertThat(config.getAll("cheeses", "type").orElse(Collections.emptyList()).subList(0, 2))
@@ -123,7 +123,8 @@ void ensureCanReadListOfMaps() {
123123
List<String> expected =
124124
Arrays.asList(
125125
"display-name=\"htmlunit\"",
126-
"stereotype={\"browserName\": \"htmlunit\",\"browserVersion\": \"chrome\"}");
126+
"stereotype={\"browserName\": \"htmlunit\",\"browserVersion\": \"chrome\"}",
127+
Config.DELIMITER);
127128
Optional<List<String>> content = config.getAll("node", "driver-configuration");
128129
assertThat(content).isEqualTo(Optional.of(expected));
129130
}

java/test/org/openqa/selenium/grid/config/TomlConfigTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ void shouldContainConfigFromArrayOfTables() {
5555

5656
List<String> expected =
5757
Arrays.asList(
58-
"default=\"brie\"", "name=\"soft cheese\"",
59-
"default=\"Emmental\"", "name=\"Medium-hard cheese\"");
58+
"default=\"brie\"", "name=\"soft cheese\"", Config.DELIMITER,
59+
"default=\"Emmental\"", "name=\"Medium-hard cheese\"", Config.DELIMITER);
6060
assertThat(config.getAll("cheeses", "type").orElse(Collections.emptyList()))
6161
.isEqualTo(expected);
6262
assertThat(config.getAll("cheeses", "type").orElse(Collections.emptyList()).subList(0, 2))
@@ -89,7 +89,8 @@ void ensureCanReadListOfMaps() {
8989
List<String> expected =
9090
Arrays.asList(
9191
"display-name=\"htmlunit\"",
92-
"stereotype={\"browserVersion\": \"chrome\",\"browserName\": \"htmlunit\"}");
92+
"stereotype={\"browserVersion\": \"chrome\",\"browserName\": \"htmlunit\"}",
93+
Config.DELIMITER);
9394
Optional<List<String>> content = config.getAll("node", "driver-configuration");
9495
assertThat(content).isEqualTo(Optional.of(expected));
9596
}

0 commit comments

Comments
 (0)