|
@@ -19,6 +19,7 @@ import org.junit.AssumptionViolatedException;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Collection;
|
|
|
import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
@@ -27,6 +28,7 @@ import java.util.function.Consumer;
|
|
|
import java.util.function.Predicate;
|
|
|
|
|
|
import static java.util.Collections.emptyList;
|
|
|
+import static java.util.stream.Collectors.joining;
|
|
|
|
|
|
/**
|
|
|
* Represents a section where prerequisites to run a specific test section or suite are specified. It is possible to specify preconditions
|
|
@@ -43,16 +45,23 @@ public class PrerequisiteSection {
|
|
|
private static final Set<String> FIELD_NAMES = Set.of("cluster_feature", "fixed_by");
|
|
|
}
|
|
|
|
|
|
+ record CapabilitiesCheck(String method, String path, String parameters, String capabilities) {
|
|
|
+ private static final Set<String> FIELD_NAMES = Set.of("method", "path", "parameters", "capabilities");
|
|
|
+ }
|
|
|
+
|
|
|
static class PrerequisiteSectionBuilder {
|
|
|
- String skipVersionRange = null;
|
|
|
String skipReason = null;
|
|
|
- String requiresReason = null;
|
|
|
- List<String> requiredYamlRunnerFeatures = new ArrayList<>();
|
|
|
+ String skipVersionRange = null;
|
|
|
List<String> skipOperatingSystems = new ArrayList<>();
|
|
|
List<KnownIssue> skipKnownIssues = new ArrayList<>();
|
|
|
String skipAwaitsFix = null;
|
|
|
Set<String> skipClusterFeatures = new HashSet<>();
|
|
|
+ List<CapabilitiesCheck> skipCapabilities = new ArrayList<>();
|
|
|
+
|
|
|
+ String requiresReason = null;
|
|
|
+ List<String> requiredYamlRunnerFeatures = new ArrayList<>();
|
|
|
Set<String> requiredClusterFeatures = new HashSet<>();
|
|
|
+ List<CapabilitiesCheck> requiredCapabilities = new ArrayList<>();
|
|
|
|
|
|
enum XPackRequired {
|
|
|
NOT_SPECIFIED,
|
|
@@ -116,11 +125,21 @@ public class PrerequisiteSection {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
+ public PrerequisiteSectionBuilder skipIfCapabilities(CapabilitiesCheck capabilitiesCheck) {
|
|
|
+ skipCapabilities.add(capabilitiesCheck);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
public PrerequisiteSectionBuilder requireClusterFeature(String featureName) {
|
|
|
requiredClusterFeatures.add(featureName);
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
+ public PrerequisiteSectionBuilder requireCapabilities(CapabilitiesCheck capabilitiesCheck) {
|
|
|
+ requiredCapabilities.add(capabilitiesCheck);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
public PrerequisiteSectionBuilder skipIfOs(String osName) {
|
|
|
this.skipOperatingSystems.add(osName);
|
|
|
return this;
|
|
@@ -128,13 +147,15 @@ public class PrerequisiteSection {
|
|
|
|
|
|
void validate(XContentLocation contentLocation) {
|
|
|
if ((Strings.isEmpty(skipVersionRange))
|
|
|
- && requiredYamlRunnerFeatures.isEmpty()
|
|
|
&& skipOperatingSystems.isEmpty()
|
|
|
- && xpackRequired == XPackRequired.NOT_SPECIFIED
|
|
|
- && requiredClusterFeatures.isEmpty()
|
|
|
&& skipClusterFeatures.isEmpty()
|
|
|
+ && skipCapabilities.isEmpty()
|
|
|
&& skipKnownIssues.isEmpty()
|
|
|
- && Strings.isEmpty(skipAwaitsFix)) {
|
|
|
+ && Strings.isEmpty(skipAwaitsFix)
|
|
|
+ && xpackRequired == XPackRequired.NOT_SPECIFIED
|
|
|
+ && requiredYamlRunnerFeatures.isEmpty()
|
|
|
+ && requiredCapabilities.isEmpty()
|
|
|
+ && requiredClusterFeatures.isEmpty()) {
|
|
|
// TODO separate the validation for requires / skip when dropping parsing of legacy fields, e.g. features in skip
|
|
|
throw new ParsingException(contentLocation, "at least one predicate is mandatory within a skip or requires section");
|
|
|
}
|
|
@@ -143,11 +164,12 @@ public class PrerequisiteSection {
|
|
|
&& (Strings.isEmpty(skipVersionRange)
|
|
|
&& skipOperatingSystems.isEmpty()
|
|
|
&& skipClusterFeatures.isEmpty()
|
|
|
+ && skipCapabilities.isEmpty()
|
|
|
&& skipKnownIssues.isEmpty()) == false) {
|
|
|
throw new ParsingException(contentLocation, "reason is mandatory within this skip section");
|
|
|
}
|
|
|
|
|
|
- if (Strings.isEmpty(requiresReason) && (requiredClusterFeatures.isEmpty() == false)) {
|
|
|
+ if (Strings.isEmpty(requiresReason) && ((requiredClusterFeatures.isEmpty() && requiredCapabilities.isEmpty()) == false)) {
|
|
|
throw new ParsingException(contentLocation, "reason is mandatory within this requires section");
|
|
|
}
|
|
|
|
|
@@ -190,6 +212,13 @@ public class PrerequisiteSection {
|
|
|
if (xpackRequired == XPackRequired.YES) {
|
|
|
requiresCriteriaList.add(Prerequisites.hasXPack());
|
|
|
}
|
|
|
+ if (requiredClusterFeatures.isEmpty() == false) {
|
|
|
+ requiresCriteriaList.add(Prerequisites.requireClusterFeatures(requiredClusterFeatures));
|
|
|
+ }
|
|
|
+ if (requiredCapabilities.isEmpty() == false) {
|
|
|
+ requiresCriteriaList.add(Prerequisites.requireCapabilities(requiredCapabilities));
|
|
|
+ }
|
|
|
+
|
|
|
if (xpackRequired == XPackRequired.NO) {
|
|
|
skipCriteriaList.add(Prerequisites.hasXPack());
|
|
|
}
|
|
@@ -199,12 +228,12 @@ public class PrerequisiteSection {
|
|
|
if (skipOperatingSystems.isEmpty() == false) {
|
|
|
skipCriteriaList.add(Prerequisites.skipOnOsList(skipOperatingSystems));
|
|
|
}
|
|
|
- if (requiredClusterFeatures.isEmpty() == false) {
|
|
|
- requiresCriteriaList.add(Prerequisites.requireClusterFeatures(requiredClusterFeatures));
|
|
|
- }
|
|
|
if (skipClusterFeatures.isEmpty() == false) {
|
|
|
skipCriteriaList.add(Prerequisites.skipOnClusterFeatures(skipClusterFeatures));
|
|
|
}
|
|
|
+ if (skipCapabilities.isEmpty() == false) {
|
|
|
+ skipCriteriaList.add(Prerequisites.skipCapabilities(skipCapabilities));
|
|
|
+ }
|
|
|
if (skipKnownIssues.isEmpty() == false) {
|
|
|
skipCriteriaList.add(Prerequisites.skipOnKnownIssue(skipKnownIssues));
|
|
|
}
|
|
@@ -287,6 +316,7 @@ public class PrerequisiteSection {
|
|
|
case "os" -> parseStrings(parser, builder::skipIfOs);
|
|
|
case "cluster_features" -> parseStrings(parser, builder::skipIfClusterFeature);
|
|
|
case "known_issues" -> parseArray(parser, PrerequisiteSection::parseKnownIssue, builder::skipKnownIssue);
|
|
|
+ case "capabilities" -> parseArray(parser, PrerequisiteSection::parseCapabilities, builder::skipIfCapabilities);
|
|
|
default -> false;
|
|
|
};
|
|
|
}
|
|
@@ -337,12 +367,47 @@ public class PrerequisiteSection {
|
|
|
if (fields.keySet().equals(KnownIssue.FIELD_NAMES) == false) {
|
|
|
throw new ParsingException(
|
|
|
parser.getTokenLocation(),
|
|
|
- Strings.format("Expected fields %s, but got %s", KnownIssue.FIELD_NAMES, fields.keySet())
|
|
|
+ Strings.format("Expected all of %s, but got %s", KnownIssue.FIELD_NAMES, fields.keySet())
|
|
|
);
|
|
|
}
|
|
|
return new KnownIssue(fields.get("cluster_feature"), fields.get("fixed_by"));
|
|
|
}
|
|
|
|
|
|
+ private static CapabilitiesCheck parseCapabilities(XContentParser parser) throws IOException {
|
|
|
+ Map<String, Object> fields = parser.map();
|
|
|
+ if (CapabilitiesCheck.FIELD_NAMES.containsAll(fields.keySet()) == false) {
|
|
|
+ throw new ParsingException(
|
|
|
+ parser.getTokenLocation(),
|
|
|
+ Strings.format("Expected some of %s, but got %s", CapabilitiesCheck.FIELD_NAMES, fields.keySet())
|
|
|
+ );
|
|
|
+ }
|
|
|
+ Object path = fields.get("path");
|
|
|
+ if (path == null) {
|
|
|
+ throw new ParsingException(parser.getTokenLocation(), "path is required");
|
|
|
+ }
|
|
|
+
|
|
|
+ return new CapabilitiesCheck(
|
|
|
+ ensureString(ensureString(fields.getOrDefault("method", "GET"))),
|
|
|
+ ensureString(path),
|
|
|
+ stringArrayAsParamString("parameters", fields),
|
|
|
+ stringArrayAsParamString("capabilities", fields)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String ensureString(Object obj) {
|
|
|
+ if (obj instanceof String str) return str;
|
|
|
+ throw new IllegalArgumentException("Expected STRING, but got: " + obj);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String stringArrayAsParamString(String name, Map<String, Object> fields) {
|
|
|
+ Object value = fields.get(name);
|
|
|
+ if (value == null) return null;
|
|
|
+ if (value instanceof Collection<?> values) {
|
|
|
+ return values.stream().map(PrerequisiteSection::ensureString).collect(joining(","));
|
|
|
+ }
|
|
|
+ return ensureString(value);
|
|
|
+ }
|
|
|
+
|
|
|
static void parseRequiresSection(XContentParser parser, PrerequisiteSectionBuilder builder) throws IOException {
|
|
|
requireStartObject("requires", parser.nextToken());
|
|
|
|
|
@@ -361,6 +426,7 @@ public class PrerequisiteSection {
|
|
|
valid = switch (parser.currentName()) {
|
|
|
case "test_runner_features" -> parseStrings(parser, f -> parseFeatureField(f, builder));
|
|
|
case "cluster_features" -> parseStrings(parser, builder::requireClusterFeature);
|
|
|
+ case "capabilities" -> parseArray(parser, PrerequisiteSection::parseCapabilities, builder::requireCapabilities);
|
|
|
default -> false;
|
|
|
};
|
|
|
}
|