Home | History | Annotate | Download | only in cmd
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "Compile.h"
     18 
     19 #include "android-base/file.h"
     20 #include "android-base/stringprintf.h"
     21 #include "android-base/utf8.h"
     22 
     23 #include "io/StringStream.h"
     24 #include "io/ZipArchive.h"
     25 #include "java/AnnotationProcessor.h"
     26 #include "test/Test.h"
     27 
     28 namespace aapt {
     29 
     30 using CompilerTest = CommandTestFixture;
     31 
     32 std::string BuildPath(std::vector<std::string> args) {
     33   std::string out;
     34   if (args.empty()) {
     35     return out;
     36   }
     37   out = args[0];
     38   for (int i = 1; i < args.size(); i++) {
     39     file::AppendPath(&out, args[i]);
     40   }
     41   return out;
     42 }
     43 
     44 int TestCompile(const std::string& path, const std::string& outDir, bool legacy,
     45                 StdErrDiagnostics& diag) {
     46   std::vector<android::StringPiece> args;
     47   args.push_back(path);
     48   args.push_back("-o");
     49   args.push_back(outDir);
     50   if (legacy) {
     51     args.push_back("--legacy");
     52   }
     53   return CompileCommand(&diag).Execute(args, &std::cerr);
     54 }
     55 
     56 TEST_F(CompilerTest, MultiplePeriods) {
     57   StdErrDiagnostics diag;
     58   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
     59   const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
     60                                          "integration-tests", "CompileTest", "res"});
     61 
     62   // Resource files without periods in the file name should not throw errors
     63   const std::string path0 = BuildPath({kResDir, "values", "values.xml"});
     64   const std::string path0_out = BuildPath({kResDir, "values_values.arsc.flat"});
     65   ::android::base::utf8::unlink(path0_out.c_str());
     66   ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ false, diag), 0);
     67   ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0);
     68   ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ true, diag), 0);
     69   ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0);
     70 
     71   const std::string path1 = BuildPath({kResDir, "drawable", "image.png"});
     72   const std::string path1_out = BuildPath({kResDir, "drawable_image.png.flat"});
     73   ::android::base::utf8::unlink(path1_out.c_str());
     74   ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ false, diag), 0);
     75   ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0);
     76   ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ true, diag), 0);
     77   ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0);
     78 
     79   const std::string path2 = BuildPath({kResDir, "drawable", "image.9.png"});
     80   const std::string path2_out = BuildPath({kResDir, "drawable_image.9.png.flat"});
     81   ::android::base::utf8::unlink(path2_out.c_str());
     82   ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ false, diag), 0);
     83   ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0);
     84   ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ true, diag), 0);
     85   ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0);
     86 
     87   // Resource files with periods in the file name should fail on non-legacy compilations
     88   const std::string path3 = BuildPath({kResDir, "values", "values.all.xml"});
     89   const std::string path3_out = BuildPath({kResDir, "values_values.all.arsc.flat"});
     90   ::android::base::utf8::unlink(path3_out.c_str());
     91   ASSERT_NE(TestCompile(path3, kResDir, /** legacy */ false, diag), 0);
     92   ASSERT_NE(::android::base::utf8::unlink(path3_out.c_str()), 0);
     93   ASSERT_EQ(TestCompile(path3, kResDir, /** legacy */ true, diag), 0);
     94   ASSERT_EQ(::android::base::utf8::unlink(path3_out.c_str()), 0);
     95 
     96   const std::string path4 = BuildPath({kResDir, "drawable", "image.small.png"});
     97   const std::string path4_out = BuildPath({kResDir, "drawable_image.small.png.flat"});
     98   ::android::base::utf8::unlink(path4_out.c_str());
     99   ASSERT_NE(TestCompile(path4, kResDir, /** legacy */ false, diag), 0);
    100   ASSERT_NE(::android::base::utf8::unlink(path4_out.c_str()), 0);
    101   ASSERT_EQ(TestCompile(path4, kResDir, /** legacy */ true, diag), 0);
    102   ASSERT_EQ(::android::base::utf8::unlink(path4_out.c_str()), 0);
    103 
    104   const std::string path5 = BuildPath({kResDir, "drawable", "image.small.9.png"});
    105   const std::string path5_out = BuildPath({kResDir, "drawable_image.small.9.png.flat"});
    106   ::android::base::utf8::unlink(path5_out.c_str());
    107   ASSERT_NE(TestCompile(path5, kResDir, /** legacy */ false, diag), 0);
    108   ASSERT_NE(::android::base::utf8::unlink(path5_out.c_str()), 0);
    109   ASSERT_EQ(TestCompile(path5, kResDir, /** legacy */ true, diag), 0);
    110   ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0);
    111 }
    112 
    113 TEST_F(CompilerTest, DirInput) {
    114   StdErrDiagnostics diag;
    115   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
    116   const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
    117                                          "integration-tests", "CompileTest", "DirInput", "res"});
    118   const std::string kOutputFlata =
    119       BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests",
    120                  "CompileTest", "DirInput", "compiled.flata"});
    121   ::android::base::utf8::unlink(kOutputFlata.c_str());
    122 
    123   std::vector<android::StringPiece> args;
    124   args.push_back("--dir");
    125   args.push_back(kResDir);
    126   args.push_back("-o");
    127   args.push_back(kOutputFlata);
    128   args.push_back("-v");
    129   ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0);
    130 
    131   {
    132     // Check for the presence of the compiled files
    133     std::string err;
    134     std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err);
    135     ASSERT_NE(zip, nullptr) << err;
    136     ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr);
    137     ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr);
    138     ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr);
    139   }
    140   ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0);
    141 }
    142 
    143 TEST_F(CompilerTest, ZipInput) {
    144   StdErrDiagnostics diag;
    145   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
    146   const std::string kResZip =
    147       BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests",
    148                  "CompileTest", "ZipInput", "res.zip"});
    149   const std::string kOutputFlata =
    150       BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests",
    151                  "CompileTest", "ZipInput", "compiled.flata"});
    152 
    153   ::android::base::utf8::unlink(kOutputFlata.c_str());
    154 
    155   std::vector<android::StringPiece> args;
    156   args.push_back("--zip");
    157   args.push_back(kResZip);
    158   args.push_back("-o");
    159   args.push_back(kOutputFlata);
    160   ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0);
    161 
    162   {
    163     // Check for the presence of the compiled files
    164     std::string err;
    165     std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err);
    166     ASSERT_NE(zip, nullptr) << err;
    167     ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr);
    168     ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr);
    169     ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr);
    170   }
    171   ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0);
    172 }
    173 
    174 /*
    175  * This tests the "protection" from pseudo-translation of
    176  * non-translatable files (starting with 'donotranslate')
    177  * and strings (with the translatable="false" attribute)
    178  *
    179  * We check 4 string files, 2 translatable, and 2 not (based on file name)
    180  * Each file contains 2 strings, one translatable, one not (attribute based)
    181  * Each of these files are compiled and linked into one .apk, then we load the
    182  * strings from the apk and check if there are pseudo-translated strings.
    183  */
    184 
    185 // Using 000 and 111 because they are not changed by pseudo-translation,
    186 // making our life easier.
    187 constexpr static const char sTranslatableXmlContent[] =
    188     "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
    189     "<resources>"
    190     "  <string name=\"normal\">000</string>"
    191     "  <string name=\"non_translatable\" translatable=\"false\">111</string>"
    192     "</resources>";
    193 
    194 static void AssertTranslations(CommandTestFixture *ctf, std::string file_name,
    195     std::vector<std::string> expected) {
    196 
    197   StdErrDiagnostics diag;
    198 
    199   const std::string source_file = ctf->GetTestPath("/res/values/" + file_name + ".xml");
    200   const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name);
    201   const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk");
    202 
    203   CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent));
    204   CHECK(file::mkdirs(compiled_files_dir.data()));
    205 
    206   ASSERT_EQ(CompileCommand(&diag).Execute({
    207       source_file,
    208       "-o", compiled_files_dir,
    209       "-v",
    210       "--pseudo-localize"
    211   }, &std::cerr), 0);
    212 
    213   ASSERT_TRUE(ctf->Link({
    214       "--manifest", ctf->GetDefaultManifest(),
    215       "-o", out_apk
    216   }, compiled_files_dir, &diag));
    217 
    218   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
    219 
    220   ResourceTable* table = apk->GetResourceTable();
    221   ASSERT_NE(table, nullptr);
    222   table->string_pool.Sort();
    223 
    224   const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings =
    225       table->string_pool.strings();
    226 
    227   // The actual / expected vectors have the same size
    228   const size_t pool_size = pool_strings.size();
    229   ASSERT_EQ(pool_size, expected.size());
    230 
    231   for (size_t i = 0; i < pool_size; i++) {
    232     std::string actual = pool_strings[i]->value;
    233     ASSERT_EQ(actual, expected[i]);
    234   }
    235 }
    236 
    237 TEST_F(CompilerTest, DoNotTranslateTest) {
    238   // The first string (000) is translatable, the second is not
    239   // ar-XB uses "\u200F\u202E...\u202C\u200F"
    240   std::vector<std::string> expected_translatable = {
    241       "000", "111", // default locale
    242       "[000 one]", // en-XA
    243       "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB
    244   };
    245   AssertTranslations(this, "foo", expected_translatable);
    246   AssertTranslations(this, "foo_donottranslate", expected_translatable);
    247 
    248   // No translatable strings because these are non-translatable files
    249   std::vector<std::string> expected_not_translatable = {
    250       "000", "111", // default locale
    251   };
    252   AssertTranslations(this, "donottranslate", expected_not_translatable);
    253   AssertTranslations(this, "donottranslate_foo", expected_not_translatable);
    254 }
    255 
    256 }  // namespace aapt
    257