瀏覽代碼

add possibility to mute yaml tests by operating system (#67681)

this change adds the possibility to mute yaml tests based on operating system to avoid muting whole
tests
Hendrik Muhs 4 年之前
父節點
當前提交
4cbe61467c

+ 17 - 6
docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java

@@ -22,6 +22,7 @@ package org.elasticsearch.smoketest;
 import com.carrotsearch.randomizedtesting.annotations.Name;
 import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
 import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
+
 import org.apache.http.HttpHost;
 import org.apache.http.util.EntityUtils;
 import org.apache.lucene.util.BytesRef;
@@ -93,12 +94,22 @@ public class DocsClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
 
     @Override
     protected ClientYamlTestClient initClientYamlTestClient(
-            final ClientYamlSuiteRestSpec restSpec,
-            final RestClient restClient,
-            final List<HttpHost> hosts,
-            final Version esVersion,
-            final Version masterVersion) {
-        return new ClientYamlDocsTestClient(restSpec, restClient, hosts, esVersion, masterVersion, this::getClientBuilderWithSniffedHosts);
+        final ClientYamlSuiteRestSpec restSpec,
+        final RestClient restClient,
+        final List<HttpHost> hosts,
+        final Version esVersion,
+        final Version masterVersion,
+        final String os
+    ) {
+        return new ClientYamlDocsTestClient(
+            restSpec,
+            restClient,
+            hosts,
+            esVersion,
+            masterVersion,
+            os,
+            this::getClientBuilderWithSniffedHosts
+        );
     }
 
     @Before

+ 28 - 1
rest-api-spec/src/main/resources/rest-api-spec/test/README.asciidoc

@@ -141,7 +141,34 @@ cases where they would otherwise fail, see `default_shards` and `fips_140`.
 ....
 
 The `features` field can either be a string or an array of strings.
-The skip section requires to specify either a `version` or a `features` list.
+
+The skip section can also be used to mute tests for certain operating systems.
+This way it is not necessary to mute the whole test if a operating system
+specific problem appears.
+
+The operating system is taken from the pretty name that elasticsearch reports
+using the `GET /_nodes` API. To obtain the name from a CI build grep the logs
+for:
+
+`initializing client, minimum es version`
+
+When muting by operating system, a reason is mandatory and features must contain
+skip_os:
+
+....
+    "Parent":
+     - skip:
+          features: skip_os
+          os:       debian-8
+          reason:   memory accounting problems on debian 8, see gh#xyz
+
+     - do:
+       ... test definitions ...
+....
+
+The `os` field can either be a string or an array of strings.
+
+The skip section requires to specify either a `version`, `features` or `os` list.
 
 === Available Features
 

+ 2 - 1
test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlDocsTestClient.java

@@ -50,8 +50,9 @@ public final class ClientYamlDocsTestClient extends ClientYamlTestClient {
             final List<HttpHost> hosts,
             final Version esVersion,
             final Version masterVersion,
+            final String os,
             final CheckedSupplier<RestClientBuilder, IOException> clientBuilderWithSniffedNodes) {
-        super(restSpec, restClient, hosts, esVersion, masterVersion, clientBuilderWithSniffedNodes);
+        super(restSpec, restClient, hosts, esVersion, masterVersion, os, clientBuilderWithSniffedNodes);
     }
 
     @Override

+ 7 - 0
test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestClient.java

@@ -74,6 +74,7 @@ public class ClientYamlTestClient implements Closeable {
     private final Map<NodeSelector, RestClient> restClients = new HashMap<>();
     private final Version esVersion;
     private final Version masterVersion;
+    private final String os;
     private final CheckedSupplier<RestClientBuilder, IOException> clientBuilderWithSniffedNodes;
 
     ClientYamlTestClient(
@@ -82,12 +83,14 @@ public class ClientYamlTestClient implements Closeable {
             final List<HttpHost> hosts,
             final Version esVersion,
             final Version masterVersion,
+            final String os,
             final CheckedSupplier<RestClientBuilder, IOException> clientBuilderWithSniffedNodes) {
         assert hosts.size() > 0;
         this.restSpec = restSpec;
         this.restClients.put(NodeSelector.ANY, restClient);
         this.esVersion = esVersion;
         this.masterVersion = masterVersion;
+        this.os = os;
         this.clientBuilderWithSniffedNodes = clientBuilderWithSniffedNodes;
     }
 
@@ -99,6 +102,10 @@ public class ClientYamlTestClient implements Closeable {
         return masterVersion;
     }
 
+    public String getOs() {
+        return os;
+    }
+
     /**
      * Calls an api with the provided parameters and body
      */

+ 3 - 0
test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestExecutionContext.java

@@ -203,4 +203,7 @@ public class ClientYamlTestExecutionContext {
         return clientYamlTestClient.getMasterVersion();
     }
 
+    public String os() {
+        return clientYamlTestClient.getOs();
+    }
 }

+ 39 - 4
test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java

@@ -21,6 +21,7 @@ package org.elasticsearch.test.rest.yaml;
 
 import com.carrotsearch.randomizedtesting.RandomizedTest;
 import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
+
 import org.apache.http.HttpHost;
 import org.apache.lucene.util.TimeUnits;
 import org.elasticsearch.Version;
@@ -36,6 +37,7 @@ import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.collect.Tuple;
 import org.elasticsearch.common.io.PathUtils;
 import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.core.internal.io.IOUtils;
 import org.elasticsearch.test.rest.ESRestTestCase;
 import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestApi;
@@ -58,6 +60,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
@@ -132,8 +135,16 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase {
             Tuple<Version, Version> versionVersionTuple = readVersionsFromCatNodes(adminClient());
             final Version esVersion = versionVersionTuple.v1();
             final Version masterVersion = versionVersionTuple.v2();
-            logger.info("initializing client, minimum es version [{}], master version, [{}], hosts {}", esVersion, masterVersion, hosts);
-            clientYamlTestClient = initClientYamlTestClient(restSpec, client(), hosts, esVersion, masterVersion);
+            final String os = readOsFromNodesInfo(adminClient());
+
+            logger.info(
+                "initializing client, minimum es version [{}], master version, [{}], hosts {}, os [{}]",
+                esVersion,
+                masterVersion,
+                hosts,
+                os
+            );
+            clientYamlTestClient = initClientYamlTestClient(restSpec, client(), hosts, esVersion, masterVersion, os);
             restTestExecutionContext = new ClientYamlTestExecutionContext(clientYamlTestClient, randomizeContentType());
             adminExecutionContext = new ClientYamlTestExecutionContext(clientYamlTestClient, false);
             final String[] blacklist = resolvePathsProperty(REST_TESTS_BLACKLIST, null);
@@ -161,8 +172,9 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase {
             final RestClient restClient,
             final List<HttpHost> hosts,
             final Version esVersion,
-            final Version masterVersion) {
-        return new ClientYamlTestClient(restSpec, restClient, hosts, esVersion, masterVersion, this::getClientBuilderWithSniffedHosts);
+            final Version masterVersion,
+            final String os) {
+        return new ClientYamlTestClient(restSpec, restClient, hosts, esVersion, masterVersion, os, this::getClientBuilderWithSniffedHosts);
     }
 
     @AfterClass
@@ -334,6 +346,26 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase {
         return new Tuple<>(version, masterVersion);
     }
 
+    private String readOsFromNodesInfo(RestClient restClient) throws IOException {
+        final Request request = new Request("GET", "/_nodes/os");
+        Response response = restClient.performRequest(request);
+        ClientYamlTestResponse restTestResponse = new ClientYamlTestResponse(response);
+        Set<String> osPrettyNames = new HashSet<>();
+
+        @SuppressWarnings("unchecked")
+        final Map<String, Object> nodes = (Map<String, Object>) restTestResponse.evaluate("nodes");
+
+        for (Entry<String, Object> node : nodes.entrySet()) {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> nodeInfo = (Map<String, Object>) node.getValue();
+
+            osPrettyNames.add((String) XContentMapValues.extractValue("os.pretty_name", nodeInfo));
+        }
+
+        assert osPrettyNames.size() == 1 : "mixed os cluster found";
+        return osPrettyNames.iterator().next();
+    }
+
     protected RequestOptions getCatNodesVersionMasterRequestOptions() {
         return RequestOptions.DEFAULT;
     }
@@ -355,6 +387,9 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase {
         //skip test if test section is disabled
         assumeFalse(testCandidate.getTestSection().getSkipSection().getSkipMessage(testCandidate.getTestPath()),
             testCandidate.getTestSection().getSkipSection().skip(restTestExecutionContext.esVersion()));
+        //skip test if os is excluded
+        assumeFalse(testCandidate.getTestSection().getSkipSection().getSkipMessage(testCandidate.getTestPath()),
+            testCandidate.getTestSection().getSkipSection().skip(restTestExecutionContext.os()));
 
         //let's check that there is something to run, otherwise there might be a problem with the test section
         if (testCandidate.getTestSection().getExecutableSections().size() == 0) {

+ 31 - 5
test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/SkipSection.java

@@ -35,6 +35,7 @@ import java.util.List;
  * based on:
  * - the elasticsearch version the tests are running against
  * - a specific test feature required that might not be implemented yet by the runner
+ * - an operating system (full name, including specific Linux distributions) that might show a certain behavior
  */
 public class SkipSection {
     /**
@@ -62,6 +63,7 @@ public class SkipSection {
         String version = null;
         String reason = null;
         List<String> features = new ArrayList<>();
+        List<String> operatingSystems = new ArrayList<>();
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
                 currentFieldName = parser.currentName();
@@ -72,6 +74,8 @@ public class SkipSection {
                     reason = parser.text();
                 } else if ("features".equals(currentFieldName)) {
                     features.add(parser.text());
+                } else if ("os".equals(currentFieldName)) {
+                    operatingSystems.add(parser.text());
                 }
                 else {
                     throw new ParsingException(parser.getTokenLocation(),
@@ -82,38 +86,52 @@ public class SkipSection {
                     while(parser.nextToken() != XContentParser.Token.END_ARRAY) {
                         features.add(parser.text());
                     }
+                } else if ("os".equals(currentFieldName)) {
+                    while(parser.nextToken() != XContentParser.Token.END_ARRAY) {
+                        operatingSystems.add(parser.text());
+                    }
                 }
             }
         }
 
         parser.nextToken();
 
-        if (Strings.hasLength(version) == false && features.isEmpty()) {
-            throw new ParsingException(parser.getTokenLocation(), "version or features is mandatory within skip section");
+        if ((Strings.hasLength(version) == false) && features.isEmpty() && operatingSystems.isEmpty()) {
+            throw new ParsingException(parser.getTokenLocation(), "version, features or os is mandatory within skip section");
         }
         if (Strings.hasLength(version) && Strings.hasLength(reason) == false) {
             throw new ParsingException(parser.getTokenLocation(), "reason is mandatory within skip version section");
         }
-        return new SkipSection(version, features, reason);
+        if (operatingSystems.isEmpty() == false && Strings.hasLength(reason) == false) {
+            throw new ParsingException(parser.getTokenLocation(), "reason is mandatory within skip version section");
+        }
+        // make feature "skip_os" mandatory if os is given, this is a temporary solution until language client tests know about os
+        if (operatingSystems.isEmpty() == false && features.contains("skip_os") == false) {
+            throw new ParsingException(parser.getTokenLocation(), "if os is specified, feature skip_os must be set");
+        }
+        return new SkipSection(version, features, operatingSystems, reason);
     }
 
     public static final SkipSection EMPTY = new SkipSection();
 
     private final List<VersionRange> versionRanges;
     private final List<String> features;
+    private final List<String> operatingSystems;
     private final String reason;
 
     private SkipSection() {
         this.versionRanges = new ArrayList<>();
         this.features = new ArrayList<>();
+        this.operatingSystems = new ArrayList<>();
         this.reason = null;
     }
 
-    public SkipSection(String versionRange, List<String> features, String reason) {
+    public SkipSection(String versionRange, List<String> features,  List<String> operatingSystems, String reason) {
         assert features != null;
         this.versionRanges = parseVersionRanges(versionRange);
         assert versionRanges.isEmpty() == false;
         this.features = features;
+        this.operatingSystems = operatingSystems;
         this.reason = reason;
     }
 
@@ -129,6 +147,10 @@ public class SkipSection {
         return features;
     }
 
+    public List<String> getOperatingSystems() {
+        return operatingSystems;
+    }
+
     public String getReason() {
         return reason;
     }
@@ -141,8 +163,12 @@ public class SkipSection {
         return skip || Features.areAllSupported(features) == false;
     }
 
+    public boolean skip(String os) {
+        return this.operatingSystems.contains(os);
+    }
+
     public boolean isVersionCheck() {
-        return features.isEmpty();
+        return features.isEmpty() && operatingSystems.isEmpty();
     }
 
     public boolean isEmpty() {

+ 41 - 4
test/framework/src/test/java/org/elasticsearch/test/rest/yaml/section/ClientYamlTestSuiteTests.java

@@ -30,8 +30,10 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 import static java.util.Collections.singletonMap;
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.instanceOf;
@@ -388,6 +390,41 @@ public class ClientYamlTestSuiteTests extends AbstractClientYamlTestFragmentPars
         assertThat(e.getMessage(), containsString("duplicate test section"));
     }
 
+    public void testParseSkipOs() throws Exception {
+        parser = createParser(
+            YamlXContent.yamlXContent,
+            "\"Broken on some os\":\n"
+                + "\n"
+                + "  - skip:\n"
+                + "      features:     skip_os\n"
+                + "      os:           [\"windows95\", \"debian-5\"]\n"
+                + "      reason:      \"not supported\"\n"
+                + "\n"
+                + "  - do:\n"
+                + "      indices.get_mapping:\n"
+                + "        index: test_index\n"
+                + "        type: test_type\n"
+                + "\n"
+                + "  - match: {test_type.properties.text.type:     string}\n"
+                + "  - match: {test_type.properties.text.analyzer: whitespace}\n"
+        );
+
+        ClientYamlTestSuite restTestSuite = ClientYamlTestSuite.parse(getTestClass().getName(), getTestName(), parser);
+
+        assertThat(restTestSuite, notNullValue());
+        assertThat(restTestSuite.getName(), equalTo(getTestName()));
+        assertThat(restTestSuite.getTestSections().size(), equalTo(1));
+
+        assertThat(restTestSuite.getTestSections().get(0).getName(), equalTo("Broken on some os"));
+        assertThat(restTestSuite.getTestSections().get(0).getSkipSection().isEmpty(), equalTo(false));
+        assertThat(restTestSuite.getTestSections().get(0).getSkipSection().getReason(), equalTo("not supported"));
+        assertThat(
+            restTestSuite.getTestSections().get(0).getSkipSection().getOperatingSystems(),
+            containsInAnyOrder("windows95", "debian-5")
+        );
+        assertThat(restTestSuite.getTestSections().get(0).getSkipSection().getFeatures(), containsInAnyOrder("skip_os"));
+    }
+
     public void testAddingDoWithoutSkips() {
         int lineNumber = between(1, 10000);
         DoSection doSection = new DoSection(new XContentLocation(lineNumber, 0));
@@ -509,13 +546,13 @@ public class ClientYamlTestSuiteTests extends AbstractClientYamlTestFragmentPars
         DoSection doSection = new DoSection(new XContentLocation(lineNumber, 0));
         doSection.setExpectedWarningHeaders(singletonList("foo"));
         doSection.setApiCallSection(new ApiCallSection("test"));
-        SkipSection skipSection = new SkipSection(null, singletonList("warnings"), null);
+        SkipSection skipSection = new SkipSection(null, singletonList("warnings"), emptyList(), null);
         createTestSuite(skipSection, doSection).validate();
     }
 
     public void testAddingDoWithNodeSelectorWithSkip() {
         int lineNumber = between(1, 10000);
-        SkipSection skipSection = new SkipSection(null, singletonList("node_selector"), null);
+        SkipSection skipSection = new SkipSection(null, singletonList("node_selector"), emptyList(), null);
         DoSection doSection = new DoSection(new XContentLocation(lineNumber, 0));
         ApiCallSection apiCall = new ApiCallSection("test");
         apiCall.setNodeSelector(NodeSelector.SKIP_DEDICATED_MASTERS);
@@ -525,7 +562,7 @@ public class ClientYamlTestSuiteTests extends AbstractClientYamlTestFragmentPars
 
     public void testAddingDoWithHeadersWithSkip() {
         int lineNumber = between(1, 10000);
-        SkipSection skipSection = new SkipSection(null, singletonList("headers"), null);
+        SkipSection skipSection = new SkipSection(null, singletonList("headers"), emptyList(), null);
         DoSection doSection = new DoSection(new XContentLocation(lineNumber, 0));
         ApiCallSection apiCallSection = new ApiCallSection("test");
         apiCallSection.addHeaders(singletonMap("foo", "bar"));
@@ -535,7 +572,7 @@ public class ClientYamlTestSuiteTests extends AbstractClientYamlTestFragmentPars
 
     public void testAddingContainsWithSkip() {
         int lineNumber = between(1, 10000);
-        SkipSection skipSection = new SkipSection(null, singletonList("contains"), null);
+        SkipSection skipSection = new SkipSection(null, singletonList("contains"), emptyList(), null);
         ContainsAssertion containsAssertion = new ContainsAssertion(
             new XContentLocation(lineNumber, 0),
             randomAlphaOfLength(randomIntBetween(3, 30)),

+ 61 - 10
test/framework/src/test/java/org/elasticsearch/test/rest/yaml/section/SkipSectionTests.java

@@ -35,7 +35,7 @@ public class SkipSectionTests extends AbstractClientYamlTestFragmentParserTestCa
 
     public void testSkipMultiRange() {
         SkipSection section = new SkipSection("6.0.0 - 6.1.0, 7.1.0 - 7.5.0",
-             Collections.emptyList() , "foobar");
+             Collections.emptyList(), Collections.emptyList(), "foobar");
 
         assertFalse(section.skip(Version.CURRENT));
         assertFalse(section.skip(Version.fromString("6.2.0")));
@@ -48,29 +48,37 @@ public class SkipSectionTests extends AbstractClientYamlTestFragmentParserTestCa
         assertTrue(section.skip(Version.fromString("7.5.0")));
 
         section = new SkipSection("-  7.1.0, 7.2.0 - 7.5.0, 8.0.0 -",
-            Collections.emptyList() , "foobar");
+            Collections.emptyList(), Collections.emptyList(), "foobar");
         assertTrue(section.skip(Version.fromString("7.0.0")));
         assertTrue(section.skip(Version.fromString("7.3.0")));
         assertTrue(section.skip(Version.fromString("8.0.0")));
     }
 
     public void testSkip() {
-        SkipSection section = new SkipSection("6.0.0 - 6.1.0",
-                randomBoolean() ? Collections.emptyList() : Collections.singletonList("warnings"), "foobar");
+        SkipSection section = new SkipSection(
+            "6.0.0 - 6.1.0",
+            randomBoolean() ? Collections.emptyList() : Collections.singletonList("warnings"),
+            Collections.emptyList(),
+            "foobar"
+        );
         assertFalse(section.skip(Version.CURRENT));
         assertTrue(section.skip(Version.fromString("6.0.0")));
-        section = new SkipSection(randomBoolean() ? null : "6.0.0 - 6.1.0",
-                Collections.singletonList("boom"), "foobar");
+        section = new SkipSection(
+            randomBoolean() ? null : "6.0.0 - 6.1.0",
+            Collections.singletonList("boom"),
+            Collections.emptyList(),
+            "foobar"
+        );
         assertTrue(section.skip(Version.CURRENT));
     }
 
     public void testMessage() {
         SkipSection section = new SkipSection("6.0.0 - 6.1.0",
-                Collections.singletonList("warnings"), "foobar");
+                Collections.singletonList("warnings"), Collections.emptyList(), "foobar");
         assertEquals("[FOOBAR] skipped, reason: [foobar] unsupported features [warnings]", section.getSkipMessage("FOOBAR"));
-        section = new SkipSection(null, Collections.singletonList("warnings"), "foobar");
+        section = new SkipSection(null, Collections.singletonList("warnings"), Collections.emptyList(), "foobar");
         assertEquals("[FOOBAR] skipped, reason: [foobar] unsupported features [warnings]", section.getSkipMessage("FOOBAR"));
-        section = new SkipSection(null, Collections.singletonList("warnings"), null);
+        section = new SkipSection(null, Collections.singletonList("warnings"), Collections.emptyList(), null);
         assertEquals("[FOOBAR] skipped, unsupported features [warnings]", section.getSkipMessage("FOOBAR"));
     }
 
@@ -160,6 +168,49 @@ public class SkipSectionTests extends AbstractClientYamlTestFragmentParserTestCa
         );
 
         Exception e = expectThrows(ParsingException.class, () -> SkipSection.parse(parser));
-        assertThat(e.getMessage(), is("version or features is mandatory within skip section"));
+        assertThat(e.getMessage(), is("version, features or os is mandatory within skip section"));
+    }
+
+    public void testParseSkipSectionOsNoVersion() throws Exception {
+        parser = createParser(YamlXContent.yamlXContent,
+                "features:    [\"skip_os\", \"some_feature\"]\n" +
+                "os:          debian-9\n" +
+                "reason:      memory accounting broken, see gh#xyz\n"
+        );
+
+        SkipSection skipSection = SkipSection.parse(parser);
+        assertThat(skipSection, notNullValue());
+        assertThat(skipSection.isVersionCheck(), equalTo(false));
+        assertThat(skipSection.getFeatures().size(), equalTo(2));
+        assertThat(skipSection.getOperatingSystems().size(), equalTo(1));
+        assertThat(skipSection.getOperatingSystems().get(0), equalTo("debian-9"));
+        assertThat(skipSection.getReason(), is("memory accounting broken, see gh#xyz"));
+    }
+
+    public void testParseSkipSectionOsListNoVersion() throws Exception {
+        parser = createParser(YamlXContent.yamlXContent,
+                "features:    skip_os\n" +
+                "os:          [debian-9,windows-95,ms-dos]\n" +
+                "reason:      see gh#xyz\n"
+        );
+
+        SkipSection skipSection = SkipSection.parse(parser);
+        assertThat(skipSection, notNullValue());
+        assertThat(skipSection.isVersionCheck(), equalTo(false));
+        assertThat(skipSection.getOperatingSystems().size(), equalTo(3));
+        assertThat(skipSection.getOperatingSystems().get(0), equalTo("debian-9"));
+        assertThat(skipSection.getOperatingSystems().get(1), equalTo("windows-95"));
+        assertThat(skipSection.getOperatingSystems().get(2), equalTo("ms-dos"));
+        assertThat(skipSection.getReason(), is("see gh#xyz"));
+    }
+
+    public void testParseSkipSectionOsNoFeatureNoVersion() throws Exception {
+        parser = createParser(YamlXContent.yamlXContent,
+                "os:          debian-9\n" +
+                "reason:      memory accounting broken, see gh#xyz\n"
+        );
+
+        Exception e = expectThrows(ParsingException.class, () -> SkipSection.parse(parser));
+        assertThat(e.getMessage(), is("if os is specified, feature skip_os must be set"));
     }
 }

+ 17 - 6
x-pack/docs/src/test/java/org/elasticsearch/smoketest/XDocsClientYamlTestSuiteIT.java

@@ -6,6 +6,7 @@
 package org.elasticsearch.smoketest;
 
 import com.carrotsearch.randomizedtesting.annotations.Name;
+
 import org.apache.http.HttpHost;
 import org.elasticsearch.Version;
 import org.elasticsearch.client.RestClient;
@@ -53,12 +54,22 @@ public class XDocsClientYamlTestSuiteIT extends AbstractXPackRestTest {
 
     @Override
     protected ClientYamlTestClient initClientYamlTestClient(
-            final ClientYamlSuiteRestSpec restSpec,
-            final RestClient restClient,
-            final List<HttpHost> hosts,
-            final Version esVersion,
-            final Version masterVersion) {
-        return new ClientYamlDocsTestClient(restSpec, restClient, hosts, esVersion, masterVersion, this::getClientBuilderWithSniffedHosts);
+        final ClientYamlSuiteRestSpec restSpec,
+        final RestClient restClient,
+        final List<HttpHost> hosts,
+        final Version esVersion,
+        final Version masterVersion,
+        final String os
+    ) {
+        return new ClientYamlDocsTestClient(
+            restSpec,
+            restClient,
+            hosts,
+            esVersion,
+            masterVersion,
+            os,
+            this::getClientBuilderWithSniffedHosts
+        );
     }
 
     /**