Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7cdc437f authored by Rajesh Nyamagoud's avatar Rajesh Nyamagoud
Browse files

VTS tests added for OMAPI Vendor stable interface

Test: Run omapi VTS test cases
Bug: b/180639372
Change-Id: I4c7e3703ca1badb4514c6dcc02b91b44fcaf5b61
parent 4fe64fb4
Loading
Loading
Loading
Loading
+54 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

cc_test {
    name: "VtsHalOmapiSeAccessControlTestCases",
    defaults: [
        "VtsHalTargetTestDefaults",
        "use_libaidlvintf_gtest_helper_static",
    ],
    srcs: [
        "VtsHalOmapiSeAccessControlTestCases.cpp",
    ],
    shared_libs: [
        "libbase",
        "liblog",
        "libcutils",
        "libhidlbase",
        "libnativehelper",
        "libutils",
        "libbinder_ndk",
    ],
    static_libs: [
        "VtsHalHidlTargetTestBase",
        "android.se.omapi-V1-ndk_platform",
    ],
    cflags: [
        "-O0",
        "-g",
        "-Wall",
        "-Werror",
    ],
    require_root: true,
    test_suites: [
        "general-tests",
        "vts",
    ],
}
+428 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <aidl/android/se/omapi/BnSecureElementListener.h>
#include <aidl/android/se/omapi/ISecureElementChannel.h>
#include <aidl/android/se/omapi/ISecureElementListener.h>
#include <aidl/android/se/omapi/ISecureElementReader.h>
#include <aidl/android/se/omapi/ISecureElementService.h>
#include <aidl/android/se/omapi/ISecureElementSession.h>

#include <VtsCoreUtil.h>
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
#include <utils/String16.h>

using namespace std;
using namespace ::testing;
using namespace android;

int main(int argc, char** argv) {
    InitGoogleTest(&argc, argv);
    int status = RUN_ALL_TESTS();
    return status;
}

namespace {

class OMAPISEAccessControlTest : public TestWithParam<std::string> {
   protected:

    class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {};

    /**
     * Verifies TLV data
     *
     * @return true if the data is tlv formatted, false otherwise
     */
    bool verifyBerTlvData(std::vector<uint8_t> tlv) {
        if (tlv.size() == 0) {
            LOG(ERROR) << "Invalid tlv, null";
            return false;
        }
        int i = 0;
        if ((tlv[i++] & 0x1F) == 0x1F) {
            // extra byte for TAG field
            i++;
        }

        int len = tlv[i++] & 0xFF;
        if (len > 127) {
            // more than 1 byte for length
            int bytesLength = len - 128;
            len = 0;
            for (int j = bytesLength; j > 0; j--) {
                len += (len << 8) + (tlv[i++] & 0xFF);
            }
        }
        // Additional 2 bytes for the SW
        return (tlv.size() == (i + len + 2));
    }

    void testSelectableAid(
            std::vector<std::vector<uint8_t>> authorizedAids) {
        for (auto aid : authorizedAids) {
            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
            auto seListener = ndk::SharedRefBase::make<SEListener>();

            if (mVSReaders.size() > 0) {
                for (const auto& [name, reader] : mVSReaders) {
                    std::vector<uint8_t> selectResponse = {};
                    ASSERT_NE(reader, nullptr) << "reader is null";

                    bool status = false;
                    auto res = reader->isSecureElementPresent(&status);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_TRUE(status);

                    res = reader->openSession(&session);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_NE(session, nullptr) << "Could not open session";

                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_NE(channel, nullptr) << "Could not open channel";

                    res = channel->getSelectResponse(&selectResponse);
                    ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
                    ASSERT_GE(selectResponse.size(), 2);

                    if (channel != nullptr) channel->close();
                    if (session != nullptr) session->close();

                    ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
                    ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
                    ASSERT_TRUE(
                        verifyBerTlvData(selectResponse)) << "Select Response is not complete";
                }
            }
        }
    }

    void testUnauthorisedAid(
            std::vector<std::vector<uint8_t>> unAuthorizedAids) {
        for (auto aid : unAuthorizedAids) {
            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
            auto seListener = ndk::SharedRefBase::make<SEListener>();

            if (mVSReaders.size() > 0) {
                for (const auto& [name, reader] : mVSReaders) {
                    ASSERT_NE(reader, nullptr) << "reader is null";

                    bool status = false;
                    auto res = reader->isSecureElementPresent(&status);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_TRUE(status);

                    res = reader->openSession(&session);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_NE(session, nullptr) << "Could not open session";

                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);

                    if (channel != nullptr) channel->close();
                    if (session != nullptr) session->close();

                    if (!res.isOk()) {
                        ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
                        ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
                    }
                }
            }
        }
    }

    void testTransmitAPDU(
            std::vector<uint8_t> aid,
            std::vector<std::vector<uint8_t>> apdus) {
        for (auto apdu : apdus) {
            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
            auto seListener = ndk::SharedRefBase::make<SEListener>();

            if (mVSReaders.size() > 0) {
                for (const auto& [name, reader] : mVSReaders) {
                    ASSERT_NE(reader, nullptr) << "reader is null";
                    bool status = false;
                    std::vector<uint8_t> selectResponse = {};
                    std::vector<uint8_t> transmitResponse = {};
                    auto res = reader->isSecureElementPresent(&status);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_TRUE(status);

                    res = reader->openSession(&session);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_NE(session, nullptr) << "Could not open session";

                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_NE(channel, nullptr) << "Could not open channel";

                    res = channel->getSelectResponse(&selectResponse);
                    ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
                    ASSERT_GE(selectResponse.size(), 2);
                    ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
                    ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
                    ASSERT_TRUE(
                        verifyBerTlvData(selectResponse)) << "Select Response is not complete";

                    res = channel->transmit(apdu, &transmitResponse);
                    LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
                              << " Message: " << res.getMessage();
                    if (channel != nullptr) channel->close();
                    if (session != nullptr) session->close();
                    ASSERT_TRUE(res.isOk()) << "failed to transmit";
                }
            }
        }
    }

    void testUnauthorisedAPDU(
            std::vector<uint8_t> aid,
            std::vector<std::vector<uint8_t>> apdus) {
        for (auto apdu : apdus) {
            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
            auto seListener = ndk::SharedRefBase::make<SEListener>();

            if (mVSReaders.size() > 0) {
                for (const auto& [name, reader] : mVSReaders) {
                    ASSERT_NE(reader, nullptr) << "reader is null";
                    bool status = false;
                    std::vector<uint8_t> selectResponse = {};
                    std::vector<uint8_t> transmitResponse = {};
                    auto res = reader->isSecureElementPresent(&status);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_TRUE(status);

                    res = reader->openSession(&session);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_NE(session, nullptr) << "Could not open session";

                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
                    ASSERT_TRUE(res.isOk()) << res.getMessage();
                    ASSERT_NE(channel, nullptr) << "Could not open channel";

                    res = channel->getSelectResponse(&selectResponse);
                    ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
                    ASSERT_GE(selectResponse.size(), 2);
                    ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
                    ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
                    ASSERT_TRUE(
                        verifyBerTlvData(selectResponse)) << "Select Response is not complete";

                    res = channel->transmit(apdu, &transmitResponse);
                    LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
                              << " Message: " << res.getMessage();

                    if (channel != nullptr) channel->close();
                    if (session != nullptr) session->close();
                    if (!res.isOk()) {
                        ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
                        ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
                    }
                }
            }
        }
    }

    bool supportOMAPIReaders() {
        return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str()));
    }

    void getFirstApiLevel(int32_t* outApiLevel) {
        int32_t firstApiLevel = property_get_int32(FEATURE_SE_API_LEVEL.c_str(), -1);
        if (firstApiLevel < 0) {
            firstApiLevel = property_get_int32(FEATURE_SE_SDK_VERSION.c_str(), -1);
        }
        ASSERT_GT(firstApiLevel, 0);  // first_api_level must exist
        *outApiLevel = firstApiLevel;
        return;
    }

    bool supportsHardware() {
        bool lowRamDevice = property_get_bool(FEATURE_SE_LOW_RAM.c_str(), true);
        return !lowRamDevice || deviceSupportsFeature(FEATURE_SE_HARDWARE_WATCH.c_str()) ||
                deviceSupportsFeature(FEATURE_SE_OMAPI_SERVICE.c_str());  // android.se.omapi
    }

    void SetUp() override {
        ASSERT_TRUE(supportsHardware());
        int32_t apiLevel;
        getFirstApiLevel(&apiLevel);
        ASSERT_TRUE(apiLevel > 27);
        ASSERT_TRUE(supportOMAPIReaders());
        LOG(INFO) << "get OMAPI service with name:" << GetParam();
        ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str()));
        mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder);
        ASSERT_TRUE(mOmapiSeService);

        std::vector<std::string> readers = {};

        if (mOmapiSeService != NULL) {
            auto status = mOmapiSeService->getReaders(&readers);
            ASSERT_TRUE(status.isOk()) << status.getMessage();

            for (auto readerName : readers) {
                // Filter eSE readers only
                if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) {
                    std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader;
                    status = mOmapiSeService->getReader(readerName, &reader);
                    ASSERT_TRUE(status.isOk()) << status.getMessage();

                    mVSReaders[readerName] = reader;
                }
            }
        }
    }

    void TearDown() override {
        if (mOmapiSeService != nullptr) {
            if (mVSReaders.size() > 0) {
                for (const auto& [name, reader] : mVSReaders) {
                    reader->closeSessions();
                }
            }
        }
    }

    static inline std::string const ESE_READER_PREFIX = "eSE";
    static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
    static inline std::string const FEATURE_SE_LOW_RAM = "ro.config.low_ram";
    static inline std::string const FEATURE_SE_HARDWARE_WATCH = "android.hardware.type.watch";
    static inline std::string const FEATURE_SE_OMAPI_SERVICE = "com.android.se";
    static inline std::string const FEATURE_SE_SDK_VERSION = "ro.build.version.sdk";
    static inline std::string const FEATURE_SE_API_LEVEL = "ro.product.first_api_level";

    std::vector<uint8_t> AID_40 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x40};
    std::vector<uint8_t> AID_41 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x41};
    std::vector<uint8_t> AID_42 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x42};
    std::vector<uint8_t> AID_43 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x43};
    std::vector<uint8_t> AID_44 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x44};
    std::vector<uint8_t> AID_45 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x45};
    std::vector<uint8_t> AID_46 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x46};
    std::vector<uint8_t> AID_47 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x47};
    std::vector<uint8_t> AID_48 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x48};
    std::vector<uint8_t> AID_49 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x49};
    std::vector<uint8_t> AID_4A = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4A};
    std::vector<uint8_t> AID_4B = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4B};
    std::vector<uint8_t> AID_4C = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4C};
    std::vector<uint8_t> AID_4D = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4D};
    std::vector<uint8_t> AID_4E = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4E};
    std::vector<uint8_t> AID_4F = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4F};

    std::vector<std::vector<uint8_t>> AUTHORIZED_AID = {AID_40, AID_41, AID_42, AID_44, AID_45,
                                                        AID_47, AID_48, AID_49, AID_4A, AID_4B,
                                                        AID_4C, AID_4D, AID_4E, AID_4F};
    std::vector<std::vector<uint8_t>> UNAUTHORIZED_AID = {AID_43, AID_46};

    /* Authorized APDU for AID_40 */
    std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_40 = {
        {0x00, 0x06, 0x00, 0x00},
        {0xA0, 0x06, 0x00, 0x00},
    };
    /* Unauthorized APDU for AID_40 */
    std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_40 = {
        {0x00, 0x08, 0x00, 0x00, 0x00},
        {0x80, 0x06, 0x00, 0x00},
        {0xA0, 0x08, 0x00, 0x00, 0x00},
        {0x94, 0x06, 0x00, 0x00, 0x00},
    };

    /* Authorized APDU for AID_41 */
    std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_41 = {
        {0x94, 0x06, 0x00, 0x00},
        {0x94, 0x08, 0x00, 0x00, 0x00},
        {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
        {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}};
    /* Unauthorized APDU for AID_41 */
    std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_41 = {
        {0x00, 0x06, 0x00, 0x00},
        {0x80, 0x06, 0x00, 0x00},
        {0xA0, 0x06, 0x00, 0x00},
        {0x00, 0x08, 0x00, 0x00, 0x00},
        {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA},
        {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA},
        {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA},
        {0x80, 0x08, 0x00, 0x00, 0x00},
        {0xA0, 0x08, 0x00, 0x00, 0x00},
        {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
        {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
        {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
    };

    std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService;

    std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>>
        mVSReaders = {};
};

TEST_P(OMAPISEAccessControlTest, TestAuthorizedAID) {
    testSelectableAid(AUTHORIZED_AID);
}

TEST_P(OMAPISEAccessControlTest, TestUnauthorizedAID) {
    testUnauthorisedAid(UNAUTHORIZED_AID);
}

TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID40) {
    testTransmitAPDU(AID_40, AUTHORIZED_APDU_AID_40);
}

TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID40) {
    testUnauthorisedAPDU(AID_40, UNAUTHORIZED_APDU_AID_40);
}

TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID41) {
    testTransmitAPDU(AID_41, AUTHORIZED_APDU_AID_41);
}

TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID41) {
    testUnauthorisedAPDU(AID_41, UNAUTHORIZED_APDU_AID_41);
}

INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEAccessControlTest,
                         testing::ValuesIn(::android::getAidlHalInstanceNames(
                             aidl::android::se::omapi::ISecureElementService::descriptor)),
                         android::hardware::PrintInstanceNameToString);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEAccessControlTest);

}  // namespace
+54 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

cc_test {
    name: "VtsHalOmapiSeServiceV1_TargetTest",
    defaults: [
        "VtsHalTargetTestDefaults",
        "use_libaidlvintf_gtest_helper_static",
    ],
    srcs: [
        "VtsHalOmapiSeServiceV1_TargetTest.cpp",
    ],
    shared_libs: [
        "libbase",
        "liblog",
        "libcutils",
        "libhidlbase",
        "libnativehelper",
        "libutils",
        "libbinder_ndk",
    ],
    static_libs: [
        "VtsHalHidlTargetTestBase",
        "android.se.omapi-V1-ndk_platform",
    ],
    cflags: [
        "-O0",
        "-g",
        "-Wall",
        "-Werror",
    ],
    require_root: true,
    test_suites: [
        "general-tests",
        "vts",
    ],
}
+609 −0

File added.

Preview size limit exceeded, changes collapsed.