/* * Copyright (C) 2017 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 "configuration/ConfigurationParser.h" #include #include "androidfw/ResourceTypes.h" #include "test/Test.h" #include "xml/XmlDom.h" namespace aapt { namespace configuration { void PrintTo(const AndroidSdk& sdk, std::ostream* os) { *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1) << ", target=" << sdk.target_sdk_version.value_or_default(-1) << ", max=" << sdk.max_sdk_version.value_or_default(-1); } } // namespace configuration namespace { using ::android::ResTable_config; using configuration::Abi; using configuration::AndroidSdk; using configuration::Artifact; using configuration::PostProcessingConfiguration; using configuration::DeviceFeature; using configuration::GlTexture; using configuration::Locale; using configuration::AndroidManifest; using ::testing::ElementsAre; using xml::Element; using xml::NodeCast; constexpr const char* kValidConfig = R"( armeabi-v7a arm64-v8a x86 mips xhdpi xxhdpi xxxhdpi ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi en es fr de en es-rMX fr-rCA assets/dxt1/* android.hardware.audio.low_latency ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release )"; class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test { public: ConfigurationParserTest() : ConfigurationParser("") {} protected: StdErrDiagnostics diag_; }; TEST_F(ConfigurationParserTest, ForPath_NoFile) { auto result = ConfigurationParser::ForPath("./does_not_exist.xml"); EXPECT_FALSE(result); } TEST_F(ConfigurationParserTest, ValidateFile) { auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_); auto result = parser.Parse(); ASSERT_TRUE(result); PostProcessingConfiguration& value = result.value(); EXPECT_EQ(2ul, value.artifacts.size()); ASSERT_TRUE(value.artifact_format); EXPECT_EQ( "${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release", value.artifact_format.value() ); EXPECT_EQ(2ul, value.abi_groups.size()); EXPECT_EQ(2ul, value.abi_groups["arm"].size()); EXPECT_EQ(2ul, value.abi_groups["other"].size()); EXPECT_EQ(2ul, value.screen_density_groups.size()); EXPECT_EQ(3ul, value.screen_density_groups["large"].size()); EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size()); EXPECT_EQ(2ul, value.locale_groups.size()); EXPECT_EQ(4ul, value.locale_groups["europe"].size()); EXPECT_EQ(3ul, value.locale_groups["north-america"].size()); EXPECT_EQ(1ul, value.android_sdk_groups.size()); EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version); EXPECT_EQ(19, value.android_sdk_groups["v19"].min_sdk_version.value()); EXPECT_EQ(1ul, value.gl_texture_groups.size()); EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size()); EXPECT_EQ(1ul, value.device_feature_groups.size()); EXPECT_EQ(1ul, value.device_feature_groups["low-latency"].size()); } TEST_F(ConfigurationParserTest, InvalidNamespace) { constexpr const char* invalid_ns = R"( )"; auto result = ConfigurationParser::ForContents(invalid_ns).Parse(); ASSERT_FALSE(result); } TEST_F(ConfigurationParserTest, ArtifactAction) { PostProcessingConfiguration config; { const auto doc = test::BuildXmlDom(R"xml( )xml"); ASSERT_TRUE(artifact_handler_(&config, NodeCast(doc->root.get()), &diag_)); EXPECT_EQ(1ul, config.artifacts.size()); auto& artifact = config.artifacts.back(); EXPECT_FALSE(artifact.name); // TODO: make this fail. EXPECT_EQ(1, artifact.version); EXPECT_EQ("arm", artifact.abi_group.value()); EXPECT_EQ("large", artifact.screen_density_group.value()); EXPECT_EQ("europe", artifact.locale_group.value()); EXPECT_EQ("v19", artifact.android_sdk_group.value()); EXPECT_EQ("dxt1", artifact.gl_texture_group.value()); EXPECT_EQ("low-latency", artifact.device_feature_group.value()); } { // Perform a second action to ensure we get 2 artifacts. const auto doc = test::BuildXmlDom(R"xml( )xml"); ASSERT_TRUE(artifact_handler_(&config, NodeCast(doc.get()->root.get()), &diag_)); EXPECT_EQ(2ul, config.artifacts.size()); EXPECT_EQ(2, config.artifacts.back().version); } { // Perform a third action with a set version code. const auto doc = test::BuildXmlDom(R"xml( )xml"); ASSERT_TRUE(artifact_handler_(&config, NodeCast(doc.get()->root.get()), &diag_)); EXPECT_EQ(3ul, config.artifacts.size()); EXPECT_EQ(5, config.artifacts.back().version); } { // Perform a fourth action to ensure the version code still increments. const auto doc = test::BuildXmlDom(R"xml( )xml"); ASSERT_TRUE(artifact_handler_(&config, NodeCast(doc.get()->root.get()), &diag_)); EXPECT_EQ(4ul, config.artifacts.size()); EXPECT_EQ(6, config.artifacts.back().version); } } TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) { static constexpr const char* configuration = R"xml( > ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release )xml"; auto result = ConfigurationParser::ForContents(configuration).Parse(); ASSERT_FALSE(result); } TEST_F(ConfigurationParserTest, ArtifactFormatAction) { const auto doc = test::BuildXmlDom(R"xml( ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release )xml"); PostProcessingConfiguration config; bool ok = artifact_format_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); ASSERT_TRUE(config.artifact_format); EXPECT_EQ( "${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release", static_cast(config.artifact_format.value()) ); } TEST_F(ConfigurationParserTest, AbiGroupAction) { static constexpr const char* xml = R"xml( armeabi-v7a arm64-v8a )xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; bool ok = abi_group_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.abi_groups.size()); ASSERT_EQ(1u, config.abi_groups.count("arm")); auto& out = config.abi_groups["arm"]; ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a)); } TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { static constexpr const char* xml = R"xml( xhdpi xxhdpi xxxhdpi )xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; bool ok = screen_density_group_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.screen_density_groups.size()); ASSERT_EQ(1u, config.screen_density_groups.count("large")); ConfigDescription xhdpi; xhdpi.density = ResTable_config::DENSITY_XHIGH; ConfigDescription xxhdpi; xxhdpi.density = ResTable_config::DENSITY_XXHIGH; ConfigDescription xxxhdpi; xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH; auto& out = config.screen_density_groups["large"]; ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi)); } TEST_F(ConfigurationParserTest, LocaleGroupAction) { static constexpr const char* xml = R"xml( en es fr de )xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; bool ok = locale_group_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); ASSERT_EQ(1ul, config.locale_groups.size()); ASSERT_EQ(1u, config.locale_groups.count("europe")); const auto& out = config.locale_groups["europe"]; ConfigDescription en = test::ParseConfigOrDie("en"); ConfigDescription es = test::ParseConfigOrDie("es"); ConfigDescription fr = test::ParseConfigOrDie("fr"); ConfigDescription de = test::ParseConfigOrDie("de"); ASSERT_THAT(out, ElementsAre(en, es, fr, de)); } TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { static constexpr const char* xml = R"xml( )xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; bool ok = android_sdk_group_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); ASSERT_EQ(1ul, config.android_sdk_groups.size()); ASSERT_EQ(1u, config.android_sdk_groups.count("v19")); auto& out = config.android_sdk_groups["v19"]; AndroidSdk sdk; sdk.min_sdk_version = 19; sdk.target_sdk_version = 24; sdk.max_sdk_version = 25; sdk.manifest = AndroidManifest(); ASSERT_EQ(sdk, out); } TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) { static constexpr const char* xml = R"xml( )xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; bool ok = android_sdk_group_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); ASSERT_EQ(1ul, config.android_sdk_groups.size()); ASSERT_EQ(1u, config.android_sdk_groups.count("P")); auto& out = config.android_sdk_groups["P"]; AndroidSdk sdk; sdk.min_sdk_version = {}; // Only the latest development version is supported. sdk.target_sdk_version = 28; sdk.max_sdk_version = 28; ASSERT_EQ(sdk, out); } TEST_F(ConfigurationParserTest, GlTextureGroupAction) { static constexpr const char* xml = R"xml( assets/dxt1/main/* assets/dxt1/test/* )xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; bool ok = gl_texture_group_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.gl_texture_groups.size()); ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1")); auto& out = config.gl_texture_groups["dxt1"]; GlTexture texture{ std::string("GL_EXT_texture_compression_dxt1"), {"assets/dxt1/main/*", "assets/dxt1/test/*"} }; ASSERT_EQ(1ul, out.size()); ASSERT_EQ(texture, out[0]); } TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { static constexpr const char* xml = R"xml( android.hardware.audio.low_latency android.hardware.audio.pro )xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; bool ok = device_feature_group_handler_(&config, NodeCast(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.device_feature_groups.size()); ASSERT_EQ(1u, config.device_feature_groups.count("low-latency")); auto& out = config.device_feature_groups["low-latency"]; DeviceFeature low_latency = "android.hardware.audio.low_latency"; DeviceFeature pro = "android.hardware.audio.pro"; ASSERT_THAT(out, ElementsAre(low_latency, pro)); } // Artifact name parser test cases. TEST(ArtifactTest, Simple) { StdErrDiagnostics diag; Artifact x86; x86.abi_group = {"x86"}; auto x86_result = x86.ToArtifactName("something.${abi}.apk", "", &diag); ASSERT_TRUE(x86_result); EXPECT_EQ(x86_result.value(), "something.x86.apk"); Artifact arm; arm.abi_group = {"armeabi-v7a"}; { auto arm_result = arm.ToArtifactName("app.${abi}.apk", "", &diag); ASSERT_TRUE(arm_result); EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); } { auto arm_result = arm.ToArtifactName("app.${abi}.apk", "different_name.apk", &diag); ASSERT_TRUE(arm_result); EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); } { auto arm_result = arm.ToArtifactName("${basename}.${abi}.apk", "app.apk", &diag); ASSERT_TRUE(arm_result); EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); } { auto arm_result = arm.ToArtifactName("app.${abi}.${ext}", "app.apk", &diag); ASSERT_TRUE(arm_result); EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); } } TEST(ArtifactTest, Complex) { StdErrDiagnostics diag; Artifact artifact; artifact.abi_group = {"mips64"}; artifact.screen_density_group = {"ldpi"}; artifact.device_feature_group = {"df1"}; artifact.gl_texture_group = {"glx1"}; artifact.locale_group = {"en-AU"}; artifact.android_sdk_group = {"v26"}; { auto result = artifact.ToArtifactName( "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "", &diag); ASSERT_TRUE(result); EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); } { auto result = artifact.ToArtifactName( "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "app.apk", &diag); ASSERT_TRUE(result); EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); } { auto result = artifact.ToArtifactName( "${basename}.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "app.apk", &diag); ASSERT_TRUE(result); EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); } { auto result = artifact.ToArtifactName( "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.${ext}", "app.apk", &diag); ASSERT_TRUE(result); EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); } { auto result = artifact.ToArtifactName( "${basename}.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}", "app.apk", &diag); ASSERT_TRUE(result); EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); } } TEST(ArtifactTest, Missing) { StdErrDiagnostics diag; Artifact x86; x86.abi_group = {"x86"}; EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", "", &diag)); EXPECT_FALSE(x86.ToArtifactName("something.apk", "", &diag)); EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", "something.apk", &diag)); EXPECT_FALSE(x86.ToArtifactName("something.apk", "something.apk", &diag)); } TEST(ArtifactTest, Empty) { StdErrDiagnostics diag; Artifact artifact; EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", "", &diag)); EXPECT_TRUE(artifact.ToArtifactName("something.apk", "", &diag)); EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", "something.apk", &diag)); EXPECT_TRUE(artifact.ToArtifactName("something.apk", "something.apk", &diag)); } TEST(ArtifactTest, Repeated) { StdErrDiagnostics diag; Artifact artifact; artifact.screen_density_group = {"mdpi"}; ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", "", &diag)); EXPECT_FALSE(artifact.ToArtifactName("something.${density}.${density}.apk", "", &diag)); ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", "something.apk", &diag)); } TEST(ArtifactTest, Nesting) { StdErrDiagnostics diag; Artifact x86; x86.abi_group = {"x86"}; EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", "", &diag)); const Maybe& name = x86.ToArtifactName("something.${abi${abi}}.apk", "", &diag); ASSERT_TRUE(name); EXPECT_EQ(name.value(), "something.${abix86}.apk"); } TEST(ArtifactTest, Recursive) { StdErrDiagnostics diag; Artifact artifact; artifact.device_feature_group = {"${gl}"}; artifact.gl_texture_group = {"glx1"}; EXPECT_FALSE(artifact.ToArtifactName("app.${feature}.${gl}.apk", "", &diag)); artifact.device_feature_group = {"df1"}; artifact.gl_texture_group = {"${feature}"}; { const auto& result = artifact.ToArtifactName("app.${feature}.${gl}.apk", "", &diag); ASSERT_TRUE(result); EXPECT_EQ(result.value(), "app.df1.${feature}.apk"); } // This is an invalid case, but should be the only possible case due to the ordering of // replacement. artifact.device_feature_group = {"${gl}"}; artifact.gl_texture_group = {"glx1"}; { const auto& result = artifact.ToArtifactName("app.${feature}.apk", "", &diag); ASSERT_TRUE(result); EXPECT_EQ(result.value(), "app.glx1.apk"); } } } // namespace } // namespace aapt