1 /* 2 * Copyright (C) 2015 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 package android.databinding.compilationTest; 18 19 20 import org.apache.commons.io.FileUtils; 21 import org.apache.commons.io.filefilter.PrefixFileFilter; 22 import org.apache.commons.io.filefilter.SuffixFileFilter; 23 import org.apache.commons.lang3.StringUtils; 24 import org.junit.Test; 25 26 import android.databinding.tool.processing.ErrorMessages; 27 import android.databinding.tool.processing.ScopedErrorReport; 28 import android.databinding.tool.processing.ScopedException; 29 import android.databinding.tool.store.Location; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.net.URISyntaxException; 34 import java.util.Collection; 35 import java.util.List; 36 37 import static org.junit.Assert.assertEquals; 38 import static org.junit.Assert.assertNotEquals; 39 import static org.junit.Assert.assertNotNull; 40 import static org.junit.Assert.assertTrue; 41 import static org.junit.Assert.fail; 42 43 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 44 public class SimpleCompilationTest extends BaseCompilationTest { 45 46 @Test listTasks()47 public void listTasks() throws IOException, URISyntaxException, InterruptedException { 48 prepareProject(); 49 CompilationResult result = runGradle("tasks"); 50 assertEquals(0, result.resultCode); 51 assertTrue("there should not be any errors", StringUtils.isEmpty(result.error)); 52 assertTrue("Test sanity, empty project tasks", 53 result.resultContainsText("All tasks runnable from root project")); 54 } 55 56 @Test testEmptyCompilation()57 public void testEmptyCompilation() throws IOException, URISyntaxException, InterruptedException { 58 prepareProject(); 59 CompilationResult result = runGradle("assembleDebug"); 60 assertEquals(result.error, 0, result.resultCode); 61 assertTrue("there should not be any errors " + result.error, 62 StringUtils.isEmpty(result.error)); 63 assertTrue("Test sanity, should compile fine", 64 result.resultContainsText("BUILD SUCCESSFUL")); 65 } 66 67 @Test testMultipleConfigs()68 public void testMultipleConfigs() throws IOException, URISyntaxException, InterruptedException { 69 prepareProject(); 70 copyResourceTo("/layout/basic_layout.xml", 71 "/app/src/main/res/layout/main.xml"); 72 copyResourceTo("/layout/basic_layout.xml", 73 "/app/src/main/res/layout-sw100dp/main.xml"); 74 CompilationResult result = runGradle("assembleDebug"); 75 assertEquals(result.error, 0, result.resultCode); 76 File debugOut = new File(testFolder, 77 "app/build/intermediates/data-binding-layout-out/debug"); 78 Collection<File> layoutFiles = FileUtils.listFiles(debugOut, new SuffixFileFilter(".xml"), 79 new PrefixFileFilter("layout")); 80 assertTrue("test sanity", layoutFiles.size() > 1); 81 for (File layout : layoutFiles) { 82 final String contents = FileUtils.readFileToString(layout); 83 if (layout.getParent().contains("sw100")) { 84 assertTrue("File has wrong tag:" + layout.getPath(), 85 contents.indexOf("android:tag=\"layout-sw100dp/main_0\"") > 0); 86 } else { 87 assertTrue("File has wrong tag:" + layout.getPath() + "\n" + contents, 88 contents.indexOf("android:tag=\"layout/main_0\"") 89 > 0); 90 } 91 } 92 } 93 singleFileErrorTest(String resource, String targetFile, String expectedExtract, String errorMessage)94 private ScopedException singleFileErrorTest(String resource, String targetFile, 95 String expectedExtract, String errorMessage) 96 throws IOException, URISyntaxException, InterruptedException { 97 prepareProject(); 98 copyResourceTo(resource, targetFile); 99 CompilationResult result = runGradle("assembleDebug"); 100 assertNotEquals(0, result.resultCode); 101 ScopedException scopedException = result.getBindingException(); 102 assertNotNull(result.error, scopedException); 103 ScopedErrorReport report = scopedException.getScopedErrorReport(); 104 assertNotNull(report); 105 assertEquals(1, report.getLocations().size()); 106 Location loc = report.getLocations().get(0); 107 if (expectedExtract != null) { 108 String extract = extract(targetFile, loc); 109 assertEquals(expectedExtract, extract); 110 } 111 final File errorFile = new File(report.getFilePath()); 112 assertTrue(errorFile.exists()); 113 assertEquals(new File(testFolder, targetFile).getCanonicalFile(), 114 errorFile.getCanonicalFile()); 115 if (errorMessage != null) { 116 assertEquals(errorMessage, scopedException.getBareMessage()); 117 } 118 return scopedException; 119 } 120 121 @Test testMultipleExceptionsInDifferentFiles()122 public void testMultipleExceptionsInDifferentFiles() 123 throws IOException, URISyntaxException, InterruptedException { 124 prepareProject(); 125 copyResourceTo("/layout/undefined_variable_binding.xml", 126 "/app/src/main/res/layout/broken.xml"); 127 copyResourceTo("/layout/invalid_setter_binding.xml", 128 "/app/src/main/res/layout/invalid_setter.xml"); 129 CompilationResult result = runGradle("assembleDebug"); 130 assertNotEquals(result.output, 0, result.resultCode); 131 List<ScopedException> bindingExceptions = result.getBindingExceptions(); 132 assertEquals(result.error, 2, bindingExceptions.size()); 133 File broken = new File(testFolder, "/app/src/main/res/layout/broken.xml"); 134 File invalidSetter = new File(testFolder, "/app/src/main/res/layout/invalid_setter.xml"); 135 for (ScopedException exception : bindingExceptions) { 136 ScopedErrorReport report = exception.getScopedErrorReport(); 137 final File errorFile = new File(report.getFilePath()); 138 String message = null; 139 String expectedErrorFile = null; 140 if (errorFile.getCanonicalPath().equals(broken.getCanonicalPath())) { 141 message = String.format(ErrorMessages.UNDEFINED_VARIABLE, "myVariable"); 142 expectedErrorFile = "/app/src/main/res/layout/broken.xml"; 143 } else if (errorFile.getCanonicalPath().equals(invalidSetter.getCanonicalPath())) { 144 message = String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx", 145 String.class.getCanonicalName()); 146 expectedErrorFile = "/app/src/main/res/layout/invalid_setter.xml"; 147 } else { 148 fail("unexpected exception " + exception.getBareMessage()); 149 } 150 assertEquals(1, report.getLocations().size()); 151 Location loc = report.getLocations().get(0); 152 String extract = extract(expectedErrorFile, loc); 153 assertEquals("myVariable", extract); 154 assertEquals(message, exception.getBareMessage()); 155 } 156 } 157 158 @Test testBadSyntax()159 public void testBadSyntax() throws IOException, URISyntaxException, InterruptedException { 160 singleFileErrorTest("/layout/layout_with_bad_syntax.xml", 161 "/app/src/main/res/layout/broken.xml", 162 "myVar.length())", 163 String.format(ErrorMessages.SYNTAX_ERROR, 164 "extraneous input ')' expecting {<EOF>, ',', '.', '[', '+', '-', '*', '/', " 165 + "'%', '<<', '>>>', '>>', '<=', '>=', '>', '<', 'instanceof', " 166 + "'==', '!=', '&', '^', '|', '&&', '||', '?', '??'}")); 167 } 168 169 @Test testBrokenSyntax()170 public void testBrokenSyntax() throws IOException, URISyntaxException, InterruptedException { 171 singleFileErrorTest("/layout/layout_with_completely_broken_syntax.xml", 172 "/app/src/main/res/layout/broken.xml", 173 "new String()", 174 String.format(ErrorMessages.SYNTAX_ERROR, 175 "mismatched input 'String' expecting {<EOF>, ',', '.', '[', '+', '-', '*', " 176 + "'/', '%', '<<', '>>>', '>>', '<=', '>=', '>', '<', 'instanceof'," 177 + " '==', '!=', '&', '^', '|', '&&', '||', '?', '??'}")); 178 } 179 180 @Test testUndefinedVariable()181 public void testUndefinedVariable() throws IOException, URISyntaxException, 182 InterruptedException { 183 ScopedException ex = singleFileErrorTest("/layout/undefined_variable_binding.xml", 184 "/app/src/main/res/layout/broken.xml", "myVariable", 185 String.format(ErrorMessages.UNDEFINED_VARIABLE, "myVariable")); 186 } 187 188 @Test testInvalidSetterBinding()189 public void testInvalidSetterBinding() throws IOException, URISyntaxException, 190 InterruptedException { 191 prepareProject(); 192 ScopedException ex = singleFileErrorTest("/layout/invalid_setter_binding.xml", 193 "/app/src/main/res/layout/invalid_setter.xml", "myVariable", 194 String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx", 195 String.class.getCanonicalName())); 196 } 197 198 @Test testRootTag()199 public void testRootTag() throws IOException, URISyntaxException, 200 InterruptedException { 201 prepareProject(); 202 copyResourceTo("/layout/root_tag.xml", "/app/src/main/res/layout/root_tag.xml"); 203 CompilationResult result = runGradle("assembleDebug"); 204 assertNotEquals(0, result.resultCode); 205 assertNotNull(result.error); 206 final String expected = String.format(ErrorMessages.ROOT_TAG_NOT_SUPPORTED, "hello"); 207 assertTrue(result.error.contains(expected)); 208 } 209 210 @Test testInvalidVariableType()211 public void testInvalidVariableType() throws IOException, URISyntaxException, 212 InterruptedException { 213 prepareProject(); 214 ScopedException ex = singleFileErrorTest("/layout/invalid_variable_type.xml", 215 "/app/src/main/res/layout/invalid_variable.xml", "myVariable", 216 String.format(ErrorMessages.CANNOT_RESOLVE_TYPE, "myVariable~")); 217 } 218 219 @Test testSingleModule()220 public void testSingleModule() throws IOException, URISyntaxException, InterruptedException { 221 prepareApp(toMap(KEY_DEPENDENCIES, "compile project(':module1')", 222 KEY_SETTINGS_INCLUDES, "include ':app'\ninclude ':module1'")); 223 prepareModule("module1", "com.example.module1", toMap()); 224 copyResourceTo("/layout/basic_layout.xml", "/module1/src/main/res/layout/module_layout.xml"); 225 copyResourceTo("/layout/basic_layout.xml", "/app/src/main/res/layout/app_layout.xml"); 226 CompilationResult result = runGradle("assembleDebug"); 227 assertEquals(result.error, 0, result.resultCode); 228 } 229 230 @Test testModuleDependencyChange()231 public void testModuleDependencyChange() throws IOException, URISyntaxException, 232 InterruptedException { 233 prepareApp(toMap(KEY_DEPENDENCIES, "compile project(':module1')", 234 KEY_SETTINGS_INCLUDES, "include ':app'\ninclude ':module1'")); 235 prepareModule("module1", "com.example.module1", toMap( 236 KEY_DEPENDENCIES, "compile 'com.android.support:appcompat-v7:23.1.1'" 237 )); 238 copyResourceTo("/layout/basic_layout.xml", "/module1/src/main/res/layout/module_layout.xml"); 239 copyResourceTo("/layout/basic_layout.xml", "/app/src/main/res/layout/app_layout.xml"); 240 CompilationResult result = runGradle("assembleDebug"); 241 assertEquals(result.error, 0, result.resultCode); 242 File moduleFolder = new File(testFolder, "module1"); 243 copyResourceTo("/module_build.gradle", new File(moduleFolder, "build.gradle"), 244 toMap()); 245 result = runGradle("assembleDebug"); 246 assertEquals(result.error, 0, result.resultCode); 247 } 248 249 @Test testTwoLevelDependency()250 public void testTwoLevelDependency() throws IOException, URISyntaxException, InterruptedException { 251 prepareApp(toMap(KEY_DEPENDENCIES, "compile project(':module1')", 252 KEY_SETTINGS_INCLUDES, "include ':app'\ninclude ':module1'\n" 253 + "include ':module2'")); 254 prepareModule("module1", "com.example.module1", toMap(KEY_DEPENDENCIES, 255 "compile project(':module2')")); 256 prepareModule("module2", "com.example.module2", toMap()); 257 copyResourceTo("/layout/basic_layout.xml", 258 "/module2/src/main/res/layout/module2_layout.xml"); 259 copyResourceTo("/layout/basic_layout.xml", "/module1/src/main/res/layout/module1_layout.xml"); 260 copyResourceTo("/layout/basic_layout.xml", "/app/src/main/res/layout/app_layout.xml"); 261 CompilationResult result = runGradle("assembleDebug"); 262 assertEquals(result.error, 0, result.resultCode); 263 } 264 265 @Test testIncludeInMerge()266 public void testIncludeInMerge() throws Throwable { 267 prepareProject(); 268 copyResourceTo("/layout/merge_include.xml", "/app/src/main/res/layout/merge_include.xml"); 269 CompilationResult result = runGradle("assembleDebug"); 270 assertNotEquals(0, result.resultCode); 271 List<ScopedException> errors = ScopedException.extractErrors(result.error); 272 assertEquals(result.error, 1, errors.size()); 273 final ScopedException ex = errors.get(0); 274 final ScopedErrorReport report = ex.getScopedErrorReport(); 275 final File errorFile = new File(report.getFilePath()); 276 assertTrue(errorFile.exists()); 277 assertEquals( 278 new File(testFolder, "/app/src/main/res/layout/merge_include.xml") 279 .getCanonicalFile(), 280 errorFile.getCanonicalFile()); 281 assertEquals("Merge shouldn't support includes as root. Error message was '" + result.error, 282 ErrorMessages.INCLUDE_INSIDE_MERGE, ex.getBareMessage()); 283 } 284 } 285