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

Commit ad0c3ab7 authored by Yu Shan's avatar Yu Shan
Browse files

Convert VehicleHalDefaultConfig to JsonConfigLoader.

Test: Presubmit
Bug: 238685398
Change-Id: I4a10b3b4484f593a6311b85f2143ef0ef12f5f81
parent cc533f05
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -89,7 +89,8 @@ class JsonValueParser final {
// The main class to parse a VHAL config file in JSON format.
class JsonConfigParser {
  public:
    android::base::Result<std::vector<ConfigDeclaration>> parseJsonConfig(std::istream& is);
    android::base::Result<std::unordered_map<int32_t, ConfigDeclaration>> parseJsonConfig(
            std::istream& is);

  private:
    JsonValueParser mValueParser;
@@ -155,8 +156,13 @@ class JsonConfigLoader final {
  public:
    JsonConfigLoader();

    // Loads a JSON file stream and parses it to a list of ConfigDeclarations.
    android::base::Result<std::vector<ConfigDeclaration>> loadPropConfig(std::istream& is);
    // Loads a JSON file stream and parses it to a map from propId to ConfigDeclarations.
    android::base::Result<std::unordered_map<int32_t, ConfigDeclaration>> loadPropConfig(
            std::istream& is);

    // Loads a JSON config file and parses it to a map from propId to ConfigDeclarations.
    android::base::Result<std::unordered_map<int32_t, ConfigDeclaration>> loadPropConfig(
            const std::string& configPath);

  private:
    std::unique_ptr<jsonconfigloader_impl::JsonConfigParser> mParser;
+24 −6
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#endif  // ENABLE_VEHICLE_HAL_TEST_PROPERTIES

#include <android-base/strings.h>
#include <fstream>

namespace android {
namespace hardware {
@@ -509,25 +510,32 @@ std::optional<ConfigDeclaration> JsonConfigParser::parseEachProperty(
    return configDecl;
}

Result<std::vector<ConfigDeclaration>> JsonConfigParser::parseJsonConfig(std::istream& is) {
Result<std::unordered_map<int32_t, ConfigDeclaration>> JsonConfigParser::parseJsonConfig(
        std::istream& is) {
    Json::CharReaderBuilder builder;
    Json::Value root;
    std::vector<ConfigDeclaration> configs;
    std::unordered_map<int32_t, ConfigDeclaration> configsByPropId;
    std::string errs;
    if (!Json::parseFromStream(builder, is, &root, &errs)) {
        return Error() << "Failed to parse property config file as JSON, error: " << errs;
    }
    if (!root.isObject()) {
        return Error() << "root element must be an object";
    }
    if (!root.isMember("properties") || !root["properties"].isArray()) {
        return Error() << "Missing 'properties' field in root or the field is not an array";
    }
    Json::Value properties = root["properties"];
    std::vector<std::string> errors;
    for (unsigned int i = 0; i < properties.size(); i++) {
        if (auto maybeConfig = parseEachProperty(properties[i], &errors); maybeConfig.has_value()) {
            configs.push_back(std::move(maybeConfig.value()));
            configsByPropId[maybeConfig.value().config.prop] = std::move(maybeConfig.value());
        }
    }
    if (!errors.empty()) {
        return Error() << android::base::Join(errors, '\n');
    }
    return configs;
    return configsByPropId;
}

}  // namespace jsonconfigloader_impl
@@ -536,11 +544,21 @@ JsonConfigLoader::JsonConfigLoader() {
    mParser = std::make_unique<jsonconfigloader_impl::JsonConfigParser>();
}

android::base::Result<std::vector<ConfigDeclaration>> JsonConfigLoader::loadPropConfig(
        std::istream& is) {
android::base::Result<std::unordered_map<int32_t, ConfigDeclaration>>
JsonConfigLoader::loadPropConfig(std::istream& is) {
    return mParser->parseJsonConfig(is);
}

android::base::Result<std::unordered_map<int32_t, ConfigDeclaration>>
JsonConfigLoader::loadPropConfig(const std::string& configPath) {
    std::ifstream ifs(configPath.c_str());
    if (!ifs) {
        return android::base::Error() << "couldn't open " << configPath << " for parsing.";
    }

    return loadPropConfig(ifs);
}

}  // namespace vehicle
}  // namespace automotive
}  // namespace hardware
+75 −45
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ class JsonConfigLoaderUnitTest : public ::testing::Test {
    JsonConfigLoader mLoader;
};

TEST_F(JsonConfigLoaderUnitTest, TestBasic) {
TEST_F(JsonConfigLoaderUnitTest, testBasic) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -53,11 +53,39 @@ TEST_F(JsonConfigLoaderUnitTest, TestBasic) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);
    ASSERT_EQ(configs.begin()->second.config.prop, 291504388);
}

TEST_F(JsonConfigLoaderUnitTest, testRootNotObject) {
    std::istringstream iss(R"(
    []
    )");

    ASSERT_FALSE(mLoader.loadPropConfig(iss).ok()) << "root is not an object must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, testMissingPropertiesField) {
    std::istringstream iss(R"(
    {
        "abcd": 1234
    }
    )");

    ASSERT_FALSE(mLoader.loadPropConfig(iss).ok()) << "Missing 'properties' field must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, testPropertiesFieldNotArray) {
    std::istringstream iss(R"(
    {
        "properties': {'a': 'b'}
    }
    )");

    ASSERT_EQ(configs[0].config.prop, 291504388);
    ASSERT_FALSE(mLoader.loadPropConfig(iss).ok())
            << "'properties' field is not an array must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestPropertyIsEnum) {
TEST_F(JsonConfigLoaderUnitTest, testPropertyIsEnum) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -72,11 +100,10 @@ TEST_F(JsonConfigLoaderUnitTest, TestPropertyIsEnum) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    ASSERT_EQ(configs[0].config.prop, toInt(VehicleProperty::INFO_FUEL_CAPACITY));
    ASSERT_EQ(configs.begin()->second.config.prop, toInt(VehicleProperty::INFO_FUEL_CAPACITY));
}

TEST_F(JsonConfigLoaderUnitTest, TestPropertyEnum_FailInvalidEnum) {
TEST_F(JsonConfigLoaderUnitTest, testPropertyEnum_FailInvalidEnum) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -89,7 +116,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestPropertyEnum_FailInvalidEnum) {
            << "Invalid VehicleProperty enum must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestPropertyEnum_FailInvalidType) {
TEST_F(JsonConfigLoaderUnitTest, testPropertyEnum_FailInvalidType) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -102,7 +129,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestPropertyEnum_FailInvalidType) {
            << "Invalid VehicleProperty type must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestProperty_FailInvalidJson) {
TEST_F(JsonConfigLoaderUnitTest, testProperty_FailInvalidJson) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -112,7 +139,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestProperty_FailInvalidJson) {
    ASSERT_FALSE(mLoader.loadPropConfig(iss).ok()) << "Invalid JSON format must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestConfigArray) {
TEST_F(JsonConfigLoaderUnitTest, testConfigArray) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -128,10 +155,10 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigArray) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);
    ASSERT_EQ(configs[0].config.configArray, std::vector<int>({1, 2, 3}));
    ASSERT_EQ(configs.begin()->second.config.configArray, std::vector<int>({1, 2, 3}));
}

TEST_F(JsonConfigLoaderUnitTest, TestConfigArrayConstants) {
TEST_F(JsonConfigLoaderUnitTest, testConfigArrayConstants) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -147,11 +174,12 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigArrayConstants) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);
    ASSERT_EQ(configs[0].config.configArray, std::vector<int>({1, 2, FUEL_DOOR_REAR_LEFT}));
    ASSERT_EQ(configs.begin()->second.config.configArray,
              std::vector<int>({1, 2, FUEL_DOOR_REAR_LEFT}));
}

// We have special logic to deal with GALLON and US_GALLON since they share the same value.
TEST_F(JsonConfigLoaderUnitTest, TestConfigArrayUnitGallon) {
TEST_F(JsonConfigLoaderUnitTest, testConfigArrayUnitGallon) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -166,7 +194,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigArrayUnitGallon) {
    ASSERT_TRUE(result.ok()) << result.error().message();
}

TEST_F(JsonConfigLoaderUnitTest, TestConfigArrayUnitUsGallon) {
TEST_F(JsonConfigLoaderUnitTest, testConfigArrayUnitUsGallon) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -181,7 +209,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigArrayUnitUsGallon) {
    ASSERT_TRUE(result.ok()) << result.error().message();
}

TEST_F(JsonConfigLoaderUnitTest, TestConfigArray_FailInvalidEnum) {
TEST_F(JsonConfigLoaderUnitTest, testConfigArray_FailInvalidEnum) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -195,7 +223,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigArray_FailInvalidEnum) {
            << "Invalid enum in ConfigArray must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestConfigArray_FailNotArray) {
TEST_F(JsonConfigLoaderUnitTest, testConfigArray_FailNotArray) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -209,7 +237,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigArray_FailNotArray) {
            << "ConfigArray is not an array must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestConfigString) {
TEST_F(JsonConfigLoaderUnitTest, testConfigString) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -225,10 +253,10 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigString) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);
    ASSERT_EQ(configs[0].config.configString, "test");
    ASSERT_EQ(configs.begin()->second.config.configString, "test");
}

TEST_F(JsonConfigLoaderUnitTest, TestConfigString_FailNotString) {
TEST_F(JsonConfigLoaderUnitTest, testConfigString_FailNotString) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -242,7 +270,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestConfigString_FailNotString) {
            << "ConfigString is not a String must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestCheckDefaultAccessChangeMode) {
TEST_F(JsonConfigLoaderUnitTest, testCheckDefaultAccessChangeMode) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -257,12 +285,12 @@ TEST_F(JsonConfigLoaderUnitTest, TestCheckDefaultAccessChangeMode) {
    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    const VehiclePropConfig& propConfig = configs[0].config;
    const VehiclePropConfig& propConfig = configs.begin()->second.config;
    ASSERT_EQ(propConfig.access, VehiclePropertyAccess::READ);
    ASSERT_EQ(propConfig.changeMode, VehiclePropertyChangeMode::STATIC);
}

TEST_F(JsonConfigLoaderUnitTest, TestAccessOverride) {
TEST_F(JsonConfigLoaderUnitTest, testAccessOverride) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -278,12 +306,12 @@ TEST_F(JsonConfigLoaderUnitTest, TestAccessOverride) {
    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    const VehiclePropConfig& propConfig = configs[0].config;
    const VehiclePropConfig& propConfig = configs.begin()->second.config;
    ASSERT_EQ(propConfig.access, VehiclePropertyAccess::WRITE);
    ASSERT_EQ(propConfig.changeMode, VehiclePropertyChangeMode::STATIC);
}

TEST_F(JsonConfigLoaderUnitTest, TestChangeModeOverride) {
TEST_F(JsonConfigLoaderUnitTest, testChangeModeOverride) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -299,12 +327,12 @@ TEST_F(JsonConfigLoaderUnitTest, TestChangeModeOverride) {
    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    const VehiclePropConfig& propConfig = configs[0].config;
    const VehiclePropConfig& propConfig = configs.begin()->second.config;
    ASSERT_EQ(propConfig.access, VehiclePropertyAccess::READ);
    ASSERT_EQ(propConfig.changeMode, VehiclePropertyChangeMode::ON_CHANGE);
}

TEST_F(JsonConfigLoaderUnitTest, TestCustomProp) {
TEST_F(JsonConfigLoaderUnitTest, testCustomProp) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -321,12 +349,12 @@ TEST_F(JsonConfigLoaderUnitTest, TestCustomProp) {
    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    const VehiclePropConfig& propConfig = configs[0].config;
    const VehiclePropConfig& propConfig = configs.begin()->second.config;
    ASSERT_EQ(propConfig.access, VehiclePropertyAccess::WRITE);
    ASSERT_EQ(propConfig.changeMode, VehiclePropertyChangeMode::ON_CHANGE);
}

TEST_F(JsonConfigLoaderUnitTest, TestCustomProp_FailMissingAccess) {
TEST_F(JsonConfigLoaderUnitTest, testCustomProp_FailMissingAccess) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -340,7 +368,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestCustomProp_FailMissingAccess) {
            << "Missing access for custom property must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestCustomProp_FailMissingChangeMode) {
TEST_F(JsonConfigLoaderUnitTest, testCustomProp_FailMissingChangeMode) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -354,7 +382,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestCustomProp_FailMissingChangeMode) {
            << "Missing change mode for custom property must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestMinSampleRate) {
TEST_F(JsonConfigLoaderUnitTest, testMinSampleRate) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -370,10 +398,10 @@ TEST_F(JsonConfigLoaderUnitTest, TestMinSampleRate) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);
    ASSERT_EQ(configs[0].config.minSampleRate, 1);
    ASSERT_EQ(configs.begin()->second.config.minSampleRate, 1);
}

TEST_F(JsonConfigLoaderUnitTest, TestMinSampleRate_FailInvalidType) {
TEST_F(JsonConfigLoaderUnitTest, testMinSampleRate_FailInvalidType) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -387,7 +415,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestMinSampleRate_FailInvalidType) {
            << "Wrong type for MinSampleRate must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestMaxSampleRate) {
TEST_F(JsonConfigLoaderUnitTest, testMaxSampleRate) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -403,10 +431,10 @@ TEST_F(JsonConfigLoaderUnitTest, TestMaxSampleRate) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);
    ASSERT_EQ(configs[0].config.maxSampleRate, 1);
    ASSERT_EQ(configs.begin()->second.config.maxSampleRate, 1);
}

TEST_F(JsonConfigLoaderUnitTest, TestMaxSampleRate_FailInvalidType) {
TEST_F(JsonConfigLoaderUnitTest, testMaxSampleRate_FailInvalidType) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -420,7 +448,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestMaxSampleRate_FailInvalidType) {
            << "Wrong type for MaxSampleRate must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_Simple) {
TEST_F(JsonConfigLoaderUnitTest, testDefaultValue_Simple) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -438,10 +466,10 @@ TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_Simple) {

    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);
    ASSERT_EQ(configs[0].initialValue.int32Values, std::vector<int32_t>({1, 2}));
    ASSERT_EQ(configs.begin()->second.initialValue.int32Values, std::vector<int32_t>({1, 2}));
}

TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_Mixed) {
TEST_F(JsonConfigLoaderUnitTest, testDefaultValue_Mixed) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -463,7 +491,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_Mixed) {
    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    const RawPropValues& initialValue = configs[0].initialValue;
    const RawPropValues& initialValue = configs.begin()->second.initialValue;
    ASSERT_EQ(initialValue.int32Values, std::vector<int32_t>({1, FUEL_DOOR_REAR_LEFT}));
    ASSERT_EQ(initialValue.int64Values,
              std::vector<int64_t>({2, static_cast<int64_t>(FUEL_DOOR_REAR_LEFT)}));
@@ -472,7 +500,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_Mixed) {
    ASSERT_EQ(initialValue.stringValue, "abcd");
}

TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_FailNotObject) {
TEST_F(JsonConfigLoaderUnitTest, testDefaultValue_FailNotObject) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -486,7 +514,7 @@ TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_FailNotObject) {
            << "DefaultValue is not an object must cause error";
}

TEST_F(JsonConfigLoaderUnitTest, TestDefaultValue_FailInvalidType) {
TEST_F(JsonConfigLoaderUnitTest, testDefaultValue_FailInvalidType) {
    std::istringstream iss(R"(
    {
        "properties": [{
@@ -521,7 +549,7 @@ TEST_F(JsonConfigLoaderUnitTest, testAreas_Simple) {
    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    const VehiclePropConfig& config = configs[0].config;
    const VehiclePropConfig& config = configs.begin()->second.config;
    ASSERT_EQ(config.areaConfigs.size(), 1u);
    const VehicleAreaConfig& areaConfig = config.areaConfigs[0];
    ASSERT_EQ(areaConfig.minInt32Value, 1);
@@ -554,12 +582,14 @@ TEST_F(JsonConfigLoaderUnitTest, testAreas_DefaultValueForEachArea) {
    auto configs = result.value();
    ASSERT_EQ(configs.size(), 1u);

    const VehiclePropConfig& config = configs[0].config;
    const VehiclePropConfig& config = configs.begin()->second.config;
    ASSERT_EQ(config.areaConfigs.size(), 2u);
    ASSERT_EQ(config.areaConfigs[0].areaId, HVAC_LEFT);
    ASSERT_EQ(config.areaConfigs[1].areaId, HVAC_RIGHT);
    ASSERT_EQ(configs[0].initialAreaValues[HVAC_LEFT], RawPropValues{.int32Values = {1}});
    ASSERT_EQ(configs[0].initialAreaValues[HVAC_RIGHT], RawPropValues{.int32Values = {2}});
    ASSERT_EQ(configs.begin()->second.initialAreaValues[HVAC_LEFT],
              RawPropValues{.int32Values = {1}});
    ASSERT_EQ(configs.begin()->second.initialAreaValues[HVAC_RIGHT],
              RawPropValues{.int32Values = {2}});
}

TEST_F(JsonConfigLoaderUnitTest, testAreas_FailInvalidTypeForOneAreaValue) {
+24 −0
Original line number Diff line number Diff line
@@ -21,3 +21,27 @@ filegroup {
    name: "VehicleHalVendorClusterTestProperties_JSON",
    srcs: ["VendorClusterTestProperties.json"],
}

prebuilt_etc {
    name: "Prebuilt_VehicleHalDefaultProperties_JSON",
    filename_from_src: true,
    src: "DefaultProperties.json",
    sub_dir: "automotive/vhalconfig/",
    vendor: true,
}

prebuilt_etc {
    name: "Prebuilt_VehicleHalTestProperties_JSON",
    filename_from_src: true,
    src: "TestProperties.json",
    sub_dir: "automotive/vhalconfig/",
    vendor: true,
}

prebuilt_etc {
    name: "Prebuilt_VehicleHalVendorClusterTestProperties_JSON",
    filename_from_src: true,
    src: "VendorClusterTestProperties.json",
    sub_dir: "automotive/vhalconfig/",
    vendor: true,
}
+6 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <fstream>
#include <unordered_map>

namespace android {
namespace hardware {
@@ -45,7 +46,8 @@ std::string getTestFilePath(const char* filename) {
    return baseDir + "/" + filename;
}

Result<std::vector<ConfigDeclaration>> loadConfig(JsonConfigLoader& loader, const char* path) {
Result<std::unordered_map<int32_t, ConfigDeclaration>> loadConfig(JsonConfigLoader& loader,
                                                                  const char* path) {
    std::string configPath = getTestFilePath(path);
    std::ifstream ifs(configPath.c_str());
    if (!ifs) {
@@ -89,7 +91,9 @@ TEST(DefaultConfigTest, TestCompatibleWithDefaultConfigHeader) {
                                   kVendorClusterTestPropertiesConfigFile})) {
        auto result = loadConfig(loader, file);
        ASSERT_TRUE(result.ok()) << result.error().message();
        configsFromJson.insert(configsFromJson.end(), result.value().begin(), result.value().end());
        for (auto& [propId, configDeclaration] : result.value()) {
            configsFromJson.push_back(configDeclaration);
        }
    }

    ASSERT_EQ(configsFromHeaderFile.size(), configsFromJson.size());
Loading