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